diff --git a/arangod/Aql/AqlValue.cpp b/arangod/Aql/AqlValue.cpp index c075f2e60a..d89d057747 100644 --- a/arangod/Aql/AqlValue.cpp +++ b/arangod/Aql/AqlValue.cpp @@ -157,6 +157,29 @@ bool AqlValue::isString () const { return false; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not the AqlValue contains a numeric value +//////////////////////////////////////////////////////////////////////////////// + +bool AqlValue::isNumber () const { + switch (_type) { + case JSON: { + TRI_json_t const* json = _json->json(); + return TRI_IsNumberJson(json); + } + + case SHAPED: + case DOCVEC: + case RANGE: + case EMPTY: { + return false; + } + } + + TRI_ASSERT(false); + return false; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the AqlValue contains a list value //////////////////////////////////////////////////////////////////////////////// @@ -303,21 +326,12 @@ v8::Handle AqlValue::toV8 (AQL_TRANSACTION_V8* trx, TRI_ASSERT(_range != nullptr); // allocate the buffer for the result - int64_t const n = _range->_high - _range->_low + 1; + size_t const n = _range->size(); v8::Handle result = v8::Array::New(static_cast(n)); - uint32_t j = 0; // output row count - if (_range->_low <= _range->_high) { - for (int64_t i = _range->_low; i <= _range->_high; ++i) { - // is it safe to use a double here (precision loss)? - result->Set(j++, v8::Number::New(static_cast(i))); - } - } - else { - for (int64_t i = _range->_low; i >= _range->_high; --i) { - // is it safe to use a double here (precision loss)? - result->Set(j++, v8::Number::New(static_cast(i))); - } + for (uint32_t i = 0; i < n; ++i) { + // is it safe to use a double here (precision loss)? + result->Set(i, v8::Number::New(_range->at(static_cast(i)))); } return result; @@ -409,12 +423,12 @@ Json AqlValue::toJson (AQL_TRANSACTION_V8* trx, TRI_ASSERT(_range != nullptr); // allocate the buffer for the result - int64_t const n = _range->_high - _range->_low + 1; - Json json(Json::List, static_cast(n)); + size_t const n = _range->size(); + Json json(Json::List, n); - for (int64_t i = _range->_low; i <= _range->_high; ++i) { + for (size_t i = 0; i < n; ++i) { // is it safe to use a double here (precision loss)? - json.add(Json(static_cast(i))); + json.add(Json(static_cast(_range->at(i)))); } return json; @@ -430,7 +444,7 @@ Json AqlValue::toJson (AQL_TRANSACTION_V8* trx, //////////////////////////////////////////////////////////////////////////////// /// @brief extract an attribute value from the AqlValue -/// this will fail if the value is not an array +/// this will return null if the value is not an array //////////////////////////////////////////////////////////////////////////////// Json AqlValue::extractArrayMember (AQL_TRANSACTION_V8* trx, @@ -518,6 +532,76 @@ Json AqlValue::extractArrayMember (AQL_TRANSACTION_V8* trx, return Json(Json::Null); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief extract a value from a list AqlValue +/// this will return null if the value is not a list +//////////////////////////////////////////////////////////////////////////////// + +Json AqlValue::extractListMember (AQL_TRANSACTION_V8* trx, + TRI_document_collection_t const* document, + int64_t position) const { + switch (_type) { + case JSON: { + TRI_ASSERT(_json != nullptr); + TRI_json_t const* json = _json->json(); + + if (TRI_IsListJson(json)) { + size_t const length = TRI_LengthListJson(json); + if (position < 0) { + // a negative position is allowed + position = static_cast(length) + position; + } + + if (position >= 0 && position < static_cast(length)) { + // only look up the value if it is within list bounds + TRI_json_t const* found = TRI_LookupListJson(json, static_cast(position)); + + if (found != nullptr) { + return Json(TRI_UNKNOWN_MEM_ZONE, TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, found)); + } + } + } + + // attribute does not exist or something went wrong - fall-through to returning null below + return Json(Json::Null); + } + + case RANGE: { + TRI_ASSERT(_range != nullptr); + size_t const n = _range->size(); + size_t const p = static_cast(position); + + if (p < n) { + return Json(static_cast(_range->at(p))); + } + break; // fall-through to returning null + } + + case DOCVEC: { + TRI_ASSERT(_vector != nullptr); + size_t const p = static_cast(position); + + // calculate the result list length + size_t totalSize = 0; + for (auto it = _vector->begin(); it != _vector->end(); ++it) { + if (p < totalSize + (*it)->size()) { + // found the correct vector + auto vecCollection = (*it)->getDocumentCollection(0); + return (*it)->getValue(p - totalSize, 0).toJson(trx, vecCollection); + } + } + break; // fall-through to returning null + } + + case SHAPED: + case EMPTY: { + break; // fall-through to returning null + } + } + + return Json(Json::Null); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief create an AqlValue from a vector of AqlItemBlock*s //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/AqlValue.h b/arangod/Aql/AqlValue.h index c54d566e38..1437d6035f 100644 --- a/arangod/Aql/AqlValue.h +++ b/arangod/Aql/AqlValue.h @@ -156,6 +156,12 @@ namespace triagens { bool isString () const; +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not the AqlValue contains a numeric value +//////////////////////////////////////////////////////////////////////////////// + + bool isNumber () const; + //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the AqlValue contains a list value //////////////////////////////////////////////////////////////////////////////// @@ -198,13 +204,22 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// /// @brief extract an attribute value from the AqlValue -/// this will fail if the value is not an array +/// this will return null if the value is not an array //////////////////////////////////////////////////////////////////////////////// triagens::basics::Json extractArrayMember (AQL_TRANSACTION_V8*, TRI_document_collection_t const*, char const*) const; +//////////////////////////////////////////////////////////////////////////////// +/// @brief extract a value from a list AqlValue +/// this will return null if the value is not a list +//////////////////////////////////////////////////////////////////////////////// + + triagens::basics::Json extractListMember (AQL_TRANSACTION_V8*, + TRI_document_collection_t const*, + int64_t) const; + //////////////////////////////////////////////////////////////////////////////// /// @brief create an AqlValue from a vector of AqlItemBlock*s //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Ast.cpp b/arangod/Aql/Ast.cpp index 1a9c818e3c..f1eb220f62 100644 --- a/arangod/Aql/Ast.cpp +++ b/arangod/Aql/Ast.cpp @@ -166,8 +166,6 @@ AstNode* Ast::createNodeLet (char const* variableName, AstNode* Ast::createNodeFilter (AstNode const* expression) { AstNode* node = createNode(NODE_TYPE_FILTER); - node->setIntValue(static_cast(FILTER_UNKNOWN)); - node->addMember(expression); return node; @@ -846,11 +844,6 @@ void Ast::optimize () { return nullptr; } - // FILTER - if (node->type == NODE_TYPE_FILTER) { - return optimizeFilter(node); - } - // unary operators if (node->type == NODE_TYPE_OPERATOR_UNARY_PLUS || node->type == NODE_TYPE_OPERATOR_UNARY_MINUS) { @@ -907,23 +900,14 @@ void Ast::optimize () { // LET if (node->type == NODE_TYPE_LET) { - return optimizeLet(node, *static_cast(data)); + return optimizeLet(node); } return node; }; - int pass; - - // optimization pass 0 - pass = 0; - _root = traverse(_root, func, &pass); - - // optimization pass 1 - pass = 1; - _root = traverse(_root, func, &pass); - - optimizeRoot(); + // optimization + _root = traverse(_root, func, nullptr); } //////////////////////////////////////////////////////////////////////////////// @@ -957,6 +941,14 @@ std::unordered_set Ast::getReferencedVariables (AstNode const* node) return result; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief add a node to the list of nodes +//////////////////////////////////////////////////////////////////////////////// + +void Ast::addNode (AstNode* node) { + _nodes.push_back(node); +} + // ----------------------------------------------------------------------------- // --SECTION-- private methods // ----------------------------------------------------------------------------- @@ -988,43 +980,6 @@ AstNode* Ast::executeConstExpression (AstNode const* node) { return value; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief optimizes a FILTER node -//////////////////////////////////////////////////////////////////////////////// - -AstNode* Ast::optimizeFilter (AstNode* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->type == NODE_TYPE_FILTER); - TRI_ASSERT(node->numMembers() == 1); - - if (node->getIntValue(true) != static_cast(FILTER_UNKNOWN)) { - // already processed filter - return node; - } - - AstNode* operand = node->getMember(0); - if (! operand->isConstant()) { - // unable to optimize non-constant expression - return node; - } - - if (! operand->isBoolValue()) { - _query->registerError(TRI_ERROR_QUERY_INVALID_LOGICAL_VALUE); - return node; - } - - if (operand->getBoolValue()) { - // FILTER is always true - node->setIntValue(static_cast(FILTER_TRUE)); - } - else { - // FILTER is always false - node->setIntValue(static_cast(FILTER_FALSE)); - } - - return node; -} - //////////////////////////////////////////////////////////////////////////////// /// @brief optimizes the unary operators + and - /// the unary plus will be converted into a simple value node if the operand of @@ -1365,9 +1320,6 @@ AstNode* Ast::optimizeReference (AstNode* node) { // constant propagation if (variable->constValue() == nullptr) { - // note which variables we are reading - variable->increaseReferenceCount(); - return node; } @@ -1405,8 +1357,7 @@ AstNode* Ast::optimizeRange (AstNode* node) { /// @brief optimizes the LET statement //////////////////////////////////////////////////////////////////////////////// -AstNode* Ast::optimizeLet (AstNode* node, - int pass) { +AstNode* Ast::optimizeLet (AstNode* node) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->type == NODE_TYPE_LET); TRI_ASSERT(node->numMembers() == 2); @@ -1417,65 +1368,17 @@ AstNode* Ast::optimizeLet (AstNode* node, auto v = static_cast(variable->getData()); TRI_ASSERT(v != nullptr); - if (pass == 0) { - if (expression->isConstant()) { - // if the expression assigned to the LET variable is constant, we'll store - // a pointer to the const value in the variable - // further optimizations can then use this pointer and optimize further, e.g. - // LET a = 1 LET b = a + 1, c = b + a can be optimized to LET a = 1 LET b = 2 LET c = 4 - v->constValue(static_cast(expression)); - } - } - else if (pass == 1) { - if (! v->isReferenceCounted() && false) { - // this optimizes away the assignment of variables which are never read - // (i.e. assigned-only variables). this is currently not free of side-effects: - // for example, in the following query, the variable 'x' would be optimized - // away, but actually the query should fail with an error: - // LET x = SUM("foobar") RETURN 1 - // TODO: check whether the right-hand side of the assignment produces an - // error and only throw away the variable if it is side-effects-free. - - // TODO: also decrease the refcount of all variables used in the expression - // (i.e. getReferencedVariables(expression)). this might produce further unused - // variables - - // TODO: COLLECT needs all variables in the scope if they do not appear directly - // in any expression - return createNodeNop(); - } - } + if (expression->isConstant()) { + // if the expression assigned to the LET variable is constant, we'll store + // a pointer to the const value in the variable + // further optimizations can then use this pointer and optimize further, e.g. + // LET a = 1 LET b = a + 1, c = b + a can be optimized to LET a = 1 LET b = 2 LET c = 4 + v->constValue(static_cast(expression)); + } return node; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief optimizes the top-level statements -//////////////////////////////////////////////////////////////////////////////// - -void Ast::optimizeRoot() { - TRI_ASSERT(_root != nullptr); - TRI_ASSERT(_root->type == NODE_TYPE_ROOT); - -/* not yet ready for prime time... - size_t const n = _root->numMembers(); - for (size_t i = 0; i < n; ++i) { - AstNode* member = _root->getMember(i); - - if (member == nullptr) { - continue; - } - - // TODO: replace trampoline variables / expressions - // TODO: detect common sub-expressions - // TODO: remove always-true filters - // TODO: remove blocks that contains always-false filters - // TODO: pull up LET assignments - // TODO: pull up FILTER - } -*/ -} - //////////////////////////////////////////////////////////////////////////////// /// @brief create an AST node from JSON //////////////////////////////////////////////////////////////////////////////// @@ -1616,7 +1519,7 @@ AstNode* Ast::createNode (AstNodeType type) { auto node = new AstNode(type); try { - _nodes.push_back(node); + addNode(node); } catch (...) { delete node; diff --git a/arangod/Aql/Ast.h b/arangod/Aql/Ast.h index f4e7364b07..a50dab5d0c 100644 --- a/arangod/Aql/Ast.h +++ b/arangod/Aql/Ast.h @@ -58,12 +58,6 @@ namespace triagens { class Ast { - enum FilterType { - FILTER_UNKNOWN, - FILTER_TRUE, - FILTER_FALSE - }; - // ----------------------------------------------------------------------------- // --SECTION-- constructors / destructors // ----------------------------------------------------------------------------- @@ -484,6 +478,12 @@ namespace triagens { static std::unordered_set getReferencedVariables (AstNode const*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief add a node to the list of nodes +//////////////////////////////////////////////////////////////////////////////// + + void addNode (AstNode*); + // ----------------------------------------------------------------------------- // --SECTION-- private methods // ----------------------------------------------------------------------------- @@ -496,12 +496,6 @@ namespace triagens { AstNode* executeConstExpression (AstNode const*); -//////////////////////////////////////////////////////////////////////////////// -/// @brief optimizes a FILTER node -//////////////////////////////////////////////////////////////////////////////// - - AstNode* optimizeFilter (AstNode*); - //////////////////////////////////////////////////////////////////////////////// /// @brief optimizes the unary operators + and - /// the unary plus will be converted into a simple value node if the operand of @@ -562,14 +556,7 @@ namespace triagens { /// @brief optimizes the LET statement //////////////////////////////////////////////////////////////////////////////// - AstNode* optimizeLet (AstNode*, - int); - -//////////////////////////////////////////////////////////////////////////////// -/// @brief optimizes the top-level statements -//////////////////////////////////////////////////////////////////////////////// - - void optimizeRoot (); + AstNode* optimizeLet (AstNode*); //////////////////////////////////////////////////////////////////////////////// /// @brief create an AST node from JSON diff --git a/arangod/Aql/AstNode.cpp b/arangod/Aql/AstNode.cpp index c4b680cae3..881c0f1836 100644 --- a/arangod/Aql/AstNode.cpp +++ b/arangod/Aql/AstNode.cpp @@ -28,6 +28,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "Aql/AstNode.h" +#include "Aql/Ast.h" #include "Aql/Function.h" #include "Aql/Scopes.h" #include "Aql/V8Executor.h" @@ -113,136 +114,136 @@ AstNode::AstNode (AstNodeType type) TRI_InitVectorPointer(&members, TRI_UNKNOWN_MEM_ZONE); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief create the node from JSON +//////////////////////////////////////////////////////////////////////////////// -AstNode::AstNode (triagens::aql::Query* Q, - triagens::basics::Json const& json) - : type(getNodeTypeFromJson(json)) -{ +AstNode::AstNode (Ast* ast, + triagens::basics::Json const& json) + : type(getNodeTypeFromJson(json)) { + auto query = ast->query(); TRI_InitVectorPointer(&members, TRI_UNKNOWN_MEM_ZONE); - switch(type) { + switch (type) { + case NODE_TYPE_COLLECTION: + case NODE_TYPE_PARAMETER: + case NODE_TYPE_ATTRIBUTE_ACCESS: + case NODE_TYPE_FCALL_USER: + value.type = VALUE_TYPE_STRING; + setStringValue(query->registerString(JsonHelper::getStringValue(json.json(), + "name", ""), + false)); + break; + case NODE_TYPE_VALUE: { + int vType = JsonHelper::getNumericValue(json.json(), "vTypeID", 0); + validateValueType(vType); + value.type = static_cast(vType); - case NODE_TYPE_COLLECTION: - case NODE_TYPE_PARAMETER: - case NODE_TYPE_ATTRIBUTE_ACCESS: - case NODE_TYPE_FCALL_USER: - value.type = VALUE_TYPE_STRING; - setStringValue(Q->registerString(JsonHelper::getStringValue(json.json(), - "name", ""), - false)); - break; - case NODE_TYPE_VALUE: { - int vType = JsonHelper::getNumericValue(json.json(), "vTypeID", 0); - validateValueType(vType); - value.type = (AstNodeValueType) vType; + switch (value.type) { + case VALUE_TYPE_NULL: + break; + case VALUE_TYPE_BOOL: + value.value._bool = JsonHelper::getBooleanValue(json.json(), "value", false); + break; + case VALUE_TYPE_INT: + setIntValue(JsonHelper::getNumericValue(json.json(), "value", 0)); + break; + case VALUE_TYPE_DOUBLE: + setDoubleValue(JsonHelper::getNumericValue(json.json(), "value", 0.0)); + break; + case VALUE_TYPE_STRING: + setStringValue(query->registerString(JsonHelper::getStringValue(json.json(), + "value", ""), + false)); + break; + default: { + } + } + break; + } + case NODE_TYPE_VARIABLE: { + auto variable = ast->variables()->createVariable(json); + TRI_ASSERT(variable != nullptr); + setData(variable); + break; + } + case NODE_TYPE_REFERENCE: { + auto variableId = JsonHelper::getNumericValue(json.json(), "id", 0); + auto variable = ast->variables()->getVariable(variableId); - switch (value.type) { - case VALUE_TYPE_NULL: - /// todo? do we need to do anything? - break; - case VALUE_TYPE_BOOL: - value.value._bool = JsonHelper::getBooleanValue(json.json(), "value", false); - break; - case VALUE_TYPE_INT: - setIntValue(JsonHelper::getNumericValue(json.json(), "value", 0)); - break; - case VALUE_TYPE_DOUBLE: - setDoubleValue(JsonHelper::getNumericValue(json.json(), "value", 0.0)); - break; - case VALUE_TYPE_STRING: - setStringValue(Q->registerString(JsonHelper::getStringValue(json.json(), - "value", ""), - false)); - break; - default: { - } + TRI_ASSERT(variable != nullptr); + setData(variable); + break; } - break; - } - case NODE_TYPE_VARIABLE: - case NODE_TYPE_REFERENCE: - /// auto varName=JsonHelper::getStringValue(json.json(), "name"); - // auto varId=JsonHelper::getStringValue(json.json(), "id"); - setData(Q->registerVar(new Variable(json))); - break; - case NODE_TYPE_FCALL: { - setData(Q->executor()->getFunctionByName(JsonHelper::getStringValue(json.json(), "name", ""))); - break; - } - case NODE_TYPE_ARRAY_ELEMENT: - case NODE_TYPE_ARRAY: { - setStringValue(Q->registerString(JsonHelper::getStringValue(json.json(), - "name", ""), - false)); - /* - Json subNodes = json.get("subNodes"); - if (subNodes.isList()) { - int len = subNodes.size(); - for (int i = 0; i < len; i++) { - Json subNode = subNodes.at(i); - addMember (new AstNode(subNode)); - } + case NODE_TYPE_FCALL: { + setData(query->executor()->getFunctionByName(JsonHelper::getStringValue(json.json(), "name", ""))); + break; + } + case NODE_TYPE_ARRAY_ELEMENT: { + setStringValue(query->registerString(JsonHelper::getStringValue(json.json(), + "name", ""), + false)); + break; + } + case NODE_TYPE_ARRAY: + case NODE_TYPE_ROOT: + case NODE_TYPE_FOR: + case NODE_TYPE_LET: + case NODE_TYPE_FILTER: + case NODE_TYPE_RETURN: + case NODE_TYPE_REMOVE: + case NODE_TYPE_INSERT: + case NODE_TYPE_UPDATE: + case NODE_TYPE_REPLACE: + case NODE_TYPE_COLLECT: + case NODE_TYPE_SORT: + case NODE_TYPE_SORT_ELEMENT: + case NODE_TYPE_LIMIT: + case NODE_TYPE_ASSIGN: + case NODE_TYPE_OPERATOR_UNARY_PLUS: + case NODE_TYPE_OPERATOR_UNARY_MINUS: + case NODE_TYPE_OPERATOR_UNARY_NOT: + case NODE_TYPE_OPERATOR_BINARY_AND: + case NODE_TYPE_OPERATOR_BINARY_OR: + case NODE_TYPE_OPERATOR_BINARY_PLUS: + case NODE_TYPE_OPERATOR_BINARY_MINUS: + case NODE_TYPE_OPERATOR_BINARY_TIMES: + case NODE_TYPE_OPERATOR_BINARY_DIV: + case NODE_TYPE_OPERATOR_BINARY_MOD: + case NODE_TYPE_OPERATOR_BINARY_EQ: + case NODE_TYPE_OPERATOR_BINARY_NE: + case NODE_TYPE_OPERATOR_BINARY_LT: + 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_TERNARY: + case NODE_TYPE_SUBQUERY: + case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS: + case NODE_TYPE_INDEXED_ACCESS: + case NODE_TYPE_EXPAND: + case NODE_TYPE_ITERATOR: + case NODE_TYPE_LIST: + case NODE_TYPE_RANGE: + case NODE_TYPE_NOP: + //TRI_ASSERT(false); + //THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "json dserializer: node type not implemented."); + break; } - */ - break; - } - case NODE_TYPE_ROOT: - case NODE_TYPE_FOR: - case NODE_TYPE_LET: - case NODE_TYPE_FILTER: - case NODE_TYPE_RETURN: - case NODE_TYPE_REMOVE: - case NODE_TYPE_INSERT: - case NODE_TYPE_UPDATE: - case NODE_TYPE_REPLACE: - case NODE_TYPE_COLLECT: - case NODE_TYPE_SORT: - case NODE_TYPE_SORT_ELEMENT: - case NODE_TYPE_LIMIT: - case NODE_TYPE_ASSIGN: - case NODE_TYPE_OPERATOR_UNARY_PLUS: - case NODE_TYPE_OPERATOR_UNARY_MINUS: - case NODE_TYPE_OPERATOR_UNARY_NOT: - case NODE_TYPE_OPERATOR_BINARY_AND: - case NODE_TYPE_OPERATOR_BINARY_OR: - case NODE_TYPE_OPERATOR_BINARY_PLUS: - case NODE_TYPE_OPERATOR_BINARY_MINUS: - case NODE_TYPE_OPERATOR_BINARY_TIMES: - case NODE_TYPE_OPERATOR_BINARY_DIV: - case NODE_TYPE_OPERATOR_BINARY_MOD: - case NODE_TYPE_OPERATOR_BINARY_EQ: - case NODE_TYPE_OPERATOR_BINARY_NE: - case NODE_TYPE_OPERATOR_BINARY_LT: - 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_TERNARY: - case NODE_TYPE_SUBQUERY: - case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS: - case NODE_TYPE_INDEXED_ACCESS: - case NODE_TYPE_EXPAND: - case NODE_TYPE_ITERATOR: - case NODE_TYPE_LIST: - case NODE_TYPE_RANGE: - case NODE_TYPE_NOP: - //TRI_ASSERT(false); - //THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "json dserializer: node type not implemented."); - break; - } Json subNodes = json.get("subNodes"); if (subNodes.isList()) { - int len = subNodes.size(); - for (int i = 0; i < len; i++) { + size_t const len = subNodes.size(); + for (size_t i = 0; i < len; i++) { Json subNode = subNodes.at(i); - addMember (Q->registerNode(new AstNode(Q, subNode))); + addMember(new AstNode(ast, subNode)); } } - + ast->addNode(this); } + //////////////////////////////////////////////////////////////////////////////// /// @brief destroy the node //////////////////////////////////////////////////////////////////////////////// @@ -251,12 +252,11 @@ AstNode::~AstNode () { TRI_DestroyVectorPointer(&members); } - //////////////////////////////////////////////////////////////////////////////// /// @brief return the type name of a node //////////////////////////////////////////////////////////////////////////////// -const std::string& AstNode::getTypeString () const { +std::string const& AstNode::getTypeString () const { auto it = TypeNames.find(static_cast(type)); if (it != TypeNames.end()) { return (*it).second; @@ -401,34 +401,24 @@ TRI_json_t* AstNode::toJson (TRI_memory_zone_t* zone) const { if (type == NODE_TYPE_VALUE) { // dump value of "value" node + auto v = toJsonValue(zone); + + if (v == nullptr) { + TRI_FreeJson(zone, node); + THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); + } + TRI_Insert3ArrayJson(zone, node, "value", v); TRI_Insert3ArrayJson(zone, node, "vType", TRI_CreateStringCopyJson(zone, getValueTypeString().c_str())); TRI_Insert3ArrayJson(zone, node, "vTypeID", TRI_CreateNumberJson(zone, static_cast(value.type))); - switch (value.type) { - case VALUE_TYPE_NULL: - TRI_Insert3ArrayJson(zone, node, "value", TRI_CreateStringCopyJson(zone, "null")); - break; - case VALUE_TYPE_BOOL: - TRI_Insert3ArrayJson(zone, node, "value", TRI_CreateBooleanJson(zone, value.value._bool)); - break; - case VALUE_TYPE_INT: - TRI_Insert3ArrayJson(zone, node, "value", TRI_CreateNumberJson(zone, static_cast(value.value._int))); - break; - case VALUE_TYPE_DOUBLE: - TRI_Insert3ArrayJson(zone, node, "value", TRI_CreateNumberJson(zone, value.value._double)); - break; - case VALUE_TYPE_STRING: - TRI_Insert3ArrayJson(zone, node, "value", TRI_CreateStringCopyJson(zone, value.value._string)); - break; - default: { - } - } } if (type == NODE_TYPE_VARIABLE || type == NODE_TYPE_REFERENCE) { auto variable = static_cast(getData()); + TRI_ASSERT(variable != nullptr); + /// TODO: use variable.toJson()!!! TRI_Insert3ArrayJson(zone, node, "name", TRI_CreateStringCopyJson(zone, variable->name.c_str())); TRI_Insert3ArrayJson(zone, node, "id", TRI_CreateNumberJson(zone, static_cast(variable->id))); @@ -520,16 +510,14 @@ bool AstNode::toBoolean () const { bool AstNode::isSimple () const { if (type == NODE_TYPE_ATTRIBUTE_ACCESS) { TRI_ASSERT(numMembers() == 1); - return getMember(0)->isSimple(); } -/* + if (type == NODE_TYPE_INDEXED_ACCESS) { TRI_ASSERT(numMembers() == 2); - return (getMember(0)->isSimple() && getMember(1)->isSimple()); } -*/ + if (type == NODE_TYPE_REFERENCE) { return true; } @@ -560,6 +548,10 @@ bool AstNode::isSimple () const { auto member = getMember(0); return member->isSimple(); } + + if (type == NODE_TYPE_VALUE) { + return true; + } return false; } diff --git a/arangod/Aql/AstNode.h b/arangod/Aql/AstNode.h index 079a1d9692..b72ff4490d 100644 --- a/arangod/Aql/AstNode.h +++ b/arangod/Aql/AstNode.h @@ -44,6 +44,8 @@ namespace triagens { namespace aql { + class Ast; + //////////////////////////////////////////////////////////////////////////////// /// @brief enumeration of AST node value types //////////////////////////////////////////////////////////////////////////////// @@ -152,7 +154,8 @@ namespace triagens { AstNode (AstNodeType); - AstNode (triagens::aql::Query* Q, triagens::basics::Json const& json); + AstNode (Ast*, + triagens::basics::Json const& json); //////////////////////////////////////////////////////////////////////////////// /// @brief destroy the node @@ -170,15 +173,30 @@ namespace triagens { /// @brief return the type name of a node //////////////////////////////////////////////////////////////////////////////// - const std::string& getTypeString () const; + std::string const& getTypeString () const; - const std::string& getValueTypeString () const; +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the value type name of a node +//////////////////////////////////////////////////////////////////////////////// + + std::string const& getValueTypeString () const; //////////////////////////////////////////////////////////////////////////////// /// @brief checks whether we know a type of this kind; throws exception if not. //////////////////////////////////////////////////////////////////////////////// - static void validateType (int type); - static void validateValueType (int type); + + static void validateType (int type); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief checks whether we know a value type of this kind; +/// throws exception if not. +//////////////////////////////////////////////////////////////////////////////// + + static void validateValueType (int type); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief fetch a node's type from json +//////////////////////////////////////////////////////////////////////////////// static AstNodeType getNodeTypeFromJson (triagens::basics::Json const& json); @@ -227,6 +245,14 @@ namespace triagens { return (type == NODE_TYPE_VALUE && value.type == VALUE_TYPE_BOOL); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not a value node is of string type +//////////////////////////////////////////////////////////////////////////////// + + inline bool isStringValue () const { + return (type == NODE_TYPE_VALUE && value.type == VALUE_TYPE_STRING); + } + //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not a node is simple enough to be used in a simple /// expression diff --git a/arangod/Aql/Collections.h b/arangod/Aql/Collections.h index b056e043cd..4d284a6442 100644 --- a/arangod/Aql/Collections.h +++ b/arangod/Aql/Collections.h @@ -70,8 +70,8 @@ namespace triagens { return nullptr; } - void add (std::string const& name, - TRI_transaction_type_e accessType) { + Collection* add (std::string const& name, + TRI_transaction_type_e accessType) { // check if collection already is in our map auto it = _collections.find(name); @@ -84,6 +84,7 @@ namespace triagens { delete collection; throw; } + return collection; } else { // change access type from read to write @@ -92,6 +93,7 @@ namespace triagens { (*it).second->accessType = TRI_TRANSACTION_WRITE; } } + return (*it).second; } std::vector collectionNames () const { diff --git a/arangod/Aql/ExecutionBlock.cpp b/arangod/Aql/ExecutionBlock.cpp index f0fed42a66..9f97f074e4 100644 --- a/arangod/Aql/ExecutionBlock.cpp +++ b/arangod/Aql/ExecutionBlock.cpp @@ -1132,6 +1132,7 @@ AqlItemBlock* SubqueryBlock::getSome (size_t atLeast, int FilterBlock::initialize () { int res = ExecutionBlock::initialize(); + if (res != TRI_ERROR_NO_ERROR) { return res; } @@ -1144,6 +1145,12 @@ int FilterBlock::initialize () { TRI_ASSERT(it != _varOverview->varInfo.end()); _inReg = it->second.registerId; + if (en->_resultIsEmpty) { + // we know that the filter will never produce any results + // TODO: do we need to suck in (and free) data from all dependent nodes first?? + _done = true; + } + return TRI_ERROR_NO_ERROR; } @@ -1188,6 +1195,7 @@ int FilterBlock::getOrSkipSome (size_t atLeast, size_t& skipped) { TRI_ASSERT(result == nullptr && skipped == 0); + if (_done) { return TRI_ERROR_NO_ERROR; } @@ -1208,7 +1216,7 @@ int FilterBlock::getOrSkipSome (size_t atLeast, AqlItemBlock* cur = _buffer.front(); if (_chosen.size() - _pos + skipped > atMost) { // The current block of chosen ones is too large for atMost: - if(!skipping){ + if(! skipping) { unique_ptr more(cur->slice(_chosen, _pos, _pos + (atMost - skipped))); collector.push_back(more.get()); @@ -1220,7 +1228,7 @@ int FilterBlock::getOrSkipSome (size_t atLeast, else if (_pos > 0 || _chosen.size() < cur->size()) { // The current block fits into our result, but it is already // half-eaten or needs to be copied anyway: - if(!skipping){ + if (! skipping) { unique_ptr more(cur->steal(_chosen, _pos, _chosen.size())); collector.push_back(more.get()); more.release(); @@ -1234,7 +1242,7 @@ int FilterBlock::getOrSkipSome (size_t atLeast, else { // The current block fits into our result and is fresh and // takes them all, so we can just hand it on: - if(!skipping){ + if (! skipping) { collector.push_back(cur); } else { @@ -1253,7 +1261,7 @@ int FilterBlock::getOrSkipSome (size_t atLeast, } throw; } - if (!skipping) { + if (! skipping) { if (collector.size() == 1) { result = collector[0]; } @@ -2506,6 +2514,29 @@ void ExecutionBlock::VarOverview::after (ExecutionBlock *eb) { eb->_varOverview = *me; } +// ----------------------------------------------------------------------------- +// --SECTION-- class NoResultsBlock +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief initCursor, only call base +//////////////////////////////////////////////////////////////////////////////// + +int NoResultsBlock::initCursor (AqlItemBlock* items, size_t pos) { + _done = true; + return TRI_ERROR_NO_ERROR; +} + +int NoResultsBlock::getOrSkipSome (size_t atLeast, + size_t atMost, + bool skipping, + AqlItemBlock*& result, + size_t& skipped) { + + TRI_ASSERT(result == nullptr && skipped == 0); + return TRI_ERROR_NO_ERROR; +} + // Local Variables: // mode: outline-minor // outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)" diff --git a/arangod/Aql/ExecutionBlock.h b/arangod/Aql/ExecutionBlock.h index 992f36ffb8..2e4504bbb4 100644 --- a/arangod/Aql/ExecutionBlock.h +++ b/arangod/Aql/ExecutionBlock.h @@ -1237,6 +1237,53 @@ namespace triagens { }; +// ----------------------------------------------------------------------------- +// --SECTION-- NoResultsBlock +// ----------------------------------------------------------------------------- + + class NoResultsBlock : public ExecutionBlock { + + public: + + NoResultsBlock (AQL_TRANSACTION_V8* trx, SingletonNode const* ep) + : ExecutionBlock(trx, ep) { + } + + ~NoResultsBlock () { + } + + int initialize () { + return ExecutionBlock::initialize(); + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief initCursor, store a copy of the register values coming from above +//////////////////////////////////////////////////////////////////////////////// + + int initCursor (AqlItemBlock* items, size_t pos); + + bool hasMore () { + return false; + } + + int64_t count () { + return 0; + } + + int64_t remaining () { + return 0; + } + + private: + + int getOrSkipSome (size_t atLeast, + size_t atMost, + bool skipping, + AqlItemBlock*& result, + size_t& skipped); + + }; + } // namespace triagens::aql } // namespace triagens diff --git a/arangod/Aql/ExecutionNode.cpp b/arangod/Aql/ExecutionNode.cpp index 329426dde1..01ed704c56 100644 --- a/arangod/Aql/ExecutionNode.cpp +++ b/arangod/Aql/ExecutionNode.cpp @@ -33,6 +33,7 @@ using namespace triagens::basics; using namespace triagens::aql; +const static bool Optional = true; // ----------------------------------------------------------------------------- // --SECTION-- static initialization // ----------------------------------------------------------------------------- @@ -57,7 +58,8 @@ std::unordered_map const ExecutionNode::TypeNames{ { static_cast(REMOVE), "RemoveNode" }, { static_cast(INSERT), "InsertNode" }, { static_cast(UPDATE), "UpdateNode" }, - { static_cast(REPLACE), "ReplaceNode" } + { static_cast(REPLACE), "ReplaceNode" }, + { static_cast(NORESULTS), "NoResultsNode" } }; // ----------------------------------------------------------------------------- @@ -84,30 +86,29 @@ void ExecutionNode::validateType (int type) { } } -ExecutionNode* ExecutionNode::fromJsonFactory (Ast const* ast, - const Json &oneNode) { +ExecutionNode* ExecutionNode::fromJsonFactory (Ast* ast, + Json const& oneNode) { auto JsonString = oneNode.toString(); int nodeTypeID = JsonHelper::getNumericValue(oneNode.json(), "typeID", 0); - triagens::aql::Query* query = ast->query(); validateType(nodeTypeID); NodeType nodeType = (NodeType) nodeTypeID; switch (nodeType) { case SINGLETON: - return new SingletonNode(query, oneNode); + return new SingletonNode(ast, oneNode); case ENUMERATE_COLLECTION: - return new EnumerateCollectionNode(query, oneNode); + return new EnumerateCollectionNode(ast, oneNode); case ENUMERATE_LIST: - return new EnumerateListNode(query, oneNode); + return new EnumerateListNode(ast, oneNode); case FILTER: - return new FilterNode(query, oneNode); + return new FilterNode(ast, oneNode); case LIMIT: - return new LimitNode(query, oneNode); + return new LimitNode(ast, oneNode); case CALCULATION: - return new CalculationNode(query, oneNode); + return new CalculationNode(ast, oneNode); case SUBQUERY: - return new SubqueryNode(ast, query, oneNode); + return new SubqueryNode(ast, oneNode); case SORT: { Json jsonElements = oneNode.get("elements"); if (! jsonElements.isList()){ @@ -119,24 +120,19 @@ ExecutionNode* ExecutionNode::fromJsonFactory (Ast const* ast, for (size_t i = 0; i < len; i++) { Json oneJsonElement = jsonElements.at(i); bool ascending = JsonHelper::getBooleanValue(oneJsonElement.json(), "ascending", false); - Variable *v = query->registerVar(new Variable(oneJsonElement.get("inVariable"))); + Variable *v = varFromJson(ast, oneJsonElement, "inVariable"); elements.push_back(std::make_pair(v, ascending)); } - return new SortNode(query, oneNode, elements); + return new SortNode(ast, oneNode, elements); } case AGGREGATE: { - Json outVariableJson = oneNode.get("outVariable"); - Variable *outVariable = nullptr; - - if (!outVariableJson.isEmpty()) /* Optional... */ - outVariable = query->registerVar(new Variable(outVariableJson)); - + Variable *outVariable = varFromJson(ast, oneNode, "outVariable", Optional); Json jsonAaggregates = oneNode.get("aggregates"); - if (!jsonAaggregates.isList()){ - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "missing node type in valueTypeNames"); //// TODO + if (! jsonAaggregates.isList()){ + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "missing node type in valueTypeNames"); } int len = jsonAaggregates.size(); @@ -145,56 +141,60 @@ ExecutionNode* ExecutionNode::fromJsonFactory (Ast const* ast, aggregateVariables.reserve(len); for (int i = 0; i < len; i++) { Json oneJsonAggregate = jsonAaggregates.at(i); - Variable* outVariable = query->registerVar(new Variable(oneJsonAggregate.get("outVariable"))); - Variable* inVariable = query->registerVar(new Variable(oneJsonAggregate.get("inVariable"))); + Variable* outVariable = varFromJson(ast, oneJsonAggregate, "outVariable"); + Variable* inVariable = varFromJson(ast, oneJsonAggregate, "inVariable"); aggregateVariables.push_back(std::make_pair(outVariable, inVariable)); } - return new AggregateNode(query, + return new AggregateNode(ast, oneNode, outVariable, ast->variables()->variables(false), aggregateVariables); } case INSERT: - return new InsertNode(query, oneNode); + return new InsertNode(ast, oneNode); case REMOVE: - return new RemoveNode(query, oneNode); + return new RemoveNode(ast, oneNode); case REPLACE: - return new ReplaceNode(query, oneNode); + return new ReplaceNode(ast, oneNode); case UPDATE: - return new UpdateNode(query, oneNode); + return new UpdateNode(ast, oneNode); case RETURN: - return new ReturnNode(query, oneNode); + return new ReturnNode(ast, oneNode); + case NORESULTS: + return new NoResultsNode(ast, oneNode); + case INTERSECTION: - //return new (query, oneNode); case PROJECTION: - //return new (query, oneNode); case LOOKUP_JOIN: - //return new (query, oneNode); case MERGE_JOIN: - //return new (query, oneNode); case LOOKUP_INDEX_UNIQUE: - //return new (query, oneNode); case LOOKUP_INDEX_RANGE: - //return new (query, oneNode); case LOOKUP_FULL_COLLECTION: - //return new (query, oneNode); case CONCATENATION: - //return new (query, oneNode); case INDEX_RANGE: - //return new (query, oneNode); case MERGE: - //return new (query, oneNode); case REMOTE: - //return new (query, oneNode); + // TODO: handle these types of nodes + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "unhandled node type"); + case ILLEGAL: - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unhandled node type"); + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "invalid node type"); } return nullptr; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief create an ExecutionNode from JSON +//////////////////////////////////////////////////////////////////////////////// + +ExecutionNode::ExecutionNode (triagens::basics::Json const& json) + : ExecutionNode(JsonHelper::getNumericValue(json.json(), "id", 0), + JsonHelper::getNumericValue(json.json(), "estimatedCost", 0.0)) { +} + //////////////////////////////////////////////////////////////////////////////// /// @brief toJson, export an ExecutionNode to JSON //////////////////////////////////////////////////////////////////////////////// @@ -276,6 +276,31 @@ void ExecutionNode::walk (WalkerWorker* worker) { // --SECTION-- protected methods // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief factory for (optional) variables from json. +//////////////////////////////////////////////////////////////////////////////// + +Variable* ExecutionNode::varFromJson (Ast* ast, + triagens::basics::Json const& base, + const char *variableName, + bool optional) { + Json variableJson = base.get(variableName); + + if (variableJson.isEmpty()) { + if (optional) { + return nullptr; + } + else { + std::string msg; + msg += "Mandatory variable \"" + std::string(variableName) + "\"not found."; + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, msg.c_str()); + } + } + else { + return ast->variables()->createVariable(variableJson); + } +} + //////////////////////////////////////////////////////////////////////////////// /// @brief toJsonHelper, for a generic node //////////////////////////////////////////////////////////////////////////////// @@ -301,48 +326,9 @@ Json ExecutionNode::toJsonHelperGeneric (triagens::basics::Json& nodes, if(this->_estimatedCost != 0){ json("estimatedCost", Json(this->_estimatedCost)); } - /* - if (_varUsageValid) { - Json varsValid(Json::List, _varsValid.size()); - for (auto v : _varsValid) { - varsValid(v->toJson()); - } - json("varsValid", varsValid); - Json varsUsedLater(Json::List, _varsUsedLater.size()); - for (auto v : _varsUsedLater) { - varsUsedLater(v->toJson()); - } - json("varsUsedLater", varsUsedLater); - } -*/ return json; } -void ExecutionNode::fromJsonHelper (triagens::aql::Query* q, basics::Json const& base) { - this->_estimatedCost = JsonHelper::getNumericValue(base.json(), "estimatedCost", 0.0); - /* - Json varsUsedLaterJson = base.get("varsUsedLater"); - if (!varsUsedLaterJson.isEmpty()) { - int len = varsUsedLaterJson.size(); - _varsUsedLater.reserve(len); - for (int i = 0; i < len; i++) { - _varsUsedLater.insert(q->registerVar(new Variable(varsUsedLaterJson.at(i)))); - } - _varUsageValid = true; - } - - Json varsValidJson = base.get("varsValid"); - if (!varsValidJson.isEmpty()) { - int len = varsValidJson.size(); - _varsValid.reserve(len); - for (int i = 0; i < len; i++) { - _varsValid.insert(q->registerVar(new Variable(varsUsedLaterJson.at(i)))); - } - _varUsageValid = true; - } - */ -} - // ----------------------------------------------------------------------------- // --SECTION-- methods of SingletonNode // ----------------------------------------------------------------------------- @@ -351,9 +337,8 @@ void ExecutionNode::fromJsonHelper (triagens::aql::Query* q, basics::Json const& /// @brief toJson, for SingletonNode //////////////////////////////////////////////////////////////////////////////// -SingletonNode::SingletonNode (triagens::aql::Query* query, basics::Json const& base) +SingletonNode::SingletonNode (Ast* ast, basics::Json const& base) : ExecutionNode(base) { - fromJsonHelper(query, base); } void SingletonNode::toJsonHelper (triagens::basics::Json& nodes, @@ -371,12 +356,11 @@ void SingletonNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of EnumerateCollectionNode // ----------------------------------------------------------------------------- -EnumerateCollectionNode::EnumerateCollectionNode (triagens::aql::Query* q, basics::Json const& base) +EnumerateCollectionNode::EnumerateCollectionNode (Ast* ast, basics::Json const& base) : ExecutionNode(base), - _vocbase(q->vocbase()), - _collection(q->collections()->get(JsonHelper::getStringValue(base.json(), "collection", ""))), - _outVariable(q->registerVar(new Variable(base.get("outVariable")))) { - fromJsonHelper(q, base); + _vocbase(ast->query()->vocbase()), + _collection(ast->query()->collections()->add(JsonHelper::getStringValue(base.json(), "collection", ""), TRI_TRANSACTION_READ)), + _outVariable(varFromJson(ast, base, "outVariable")) { } //////////////////////////////////////////////////////////////////////////////// @@ -404,11 +388,10 @@ void EnumerateCollectionNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of EnumerateListNode // ----------------------------------------------------------------------------- -EnumerateListNode::EnumerateListNode (triagens::aql::Query* q, basics::Json const& base) +EnumerateListNode::EnumerateListNode (Ast* ast, basics::Json const& base) : ExecutionNode(base), - _inVariable(q->registerVar(new Variable(base.get("inVariable")))), - _outVariable(q->registerVar(new Variable(base.get("outVariable")))) { - fromJsonHelper(q, base); + _inVariable(varFromJson(ast, base, "inVariable")), + _outVariable(varFromJson(ast, base, "outVariable")) { } //////////////////////////////////////////////////////////////////////////////// @@ -464,11 +447,10 @@ void IndexRangeNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of LimitNode // ----------------------------------------------------------------------------- -LimitNode::LimitNode (triagens::aql::Query* query, basics::Json const& base) +LimitNode::LimitNode (Ast* ast, basics::Json const& base) : ExecutionNode(base) { _offset = JsonHelper::getNumericValue(base.json(), "offset", 0.0); _limit = JsonHelper::getNumericValue(base.json(), "limit", 0.0); - fromJsonHelper(query, base); } //////////////////////////////////////////////////////////////////////////////// @@ -493,12 +475,10 @@ void LimitNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of CalculationNode // ----------------------------------------------------------------------------- -CalculationNode::CalculationNode (triagens::aql::Query* q, basics::Json const& base) +CalculationNode::CalculationNode (Ast* ast, basics::Json const& base) : ExecutionNode(base), - _expression(new Expression(q, base)), - _outVariable(q->registerVar(new Variable(base.get("outVariable")))) { - //// _expression->fromJson(base, "expression")) -> list -> for schleife. - fromJsonHelper(q, base); + _expression(new Expression(ast, base)), + _outVariable(varFromJson(ast, base, "outVariable")) { } //////////////////////////////////////////////////////////////////////////////// @@ -525,13 +505,11 @@ void CalculationNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of SubqueryNode // ----------------------------------------------------------------------------- -SubqueryNode::SubqueryNode (Ast const* ast, - triagens::aql::Query* q, +SubqueryNode::SubqueryNode (Ast* ast, basics::Json const& base) : ExecutionNode(base), _subquery(nullptr), - _outVariable(q->registerVar(new Variable(base.get("outVariable")))) { - fromJsonHelper(q, base); + _outVariable(varFromJson(ast, base, "outVariable")) { } //////////////////////////////////////////////////////////////////////////////// @@ -619,10 +597,10 @@ std::vector SubqueryNode::getVariablesUsedHere () { // --SECTION-- methods of FilterNode // ----------------------------------------------------------------------------- -FilterNode::FilterNode (triagens::aql::Query* q, basics::Json const& base) +FilterNode::FilterNode (Ast* ast, basics::Json const& base) : ExecutionNode(base), - _inVariable(q->registerVar(new Variable(base.get("inVariable")))) { - fromJsonHelper(q, base); + _inVariable(varFromJson(ast, base, "inVariable")), + _resultIsEmpty(false) { } //////////////////////////////////////////////////////////////////////////////// @@ -646,12 +624,11 @@ void FilterNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of SortNode // ----------------------------------------------------------------------------- -SortNode::SortNode (triagens::aql::Query* query, +SortNode::SortNode (Ast* ast, basics::Json const& base, std::vector> elements) : ExecutionNode(base), _elements(elements) { - fromJsonHelper(query, base); } //////////////////////////////////////////////////////////////////////////////// @@ -681,7 +658,7 @@ void SortNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of AggregateNode // ----------------------------------------------------------------------------- -AggregateNode::AggregateNode (triagens::aql::Query* q, +AggregateNode::AggregateNode (Ast* ast, basics::Json const& base, Variable const* outVariable, std::unordered_map const& variableMap, @@ -690,7 +667,6 @@ AggregateNode::AggregateNode (triagens::aql::Query* q, _aggregateVariables(aggregateVariables), _outVariable(outVariable), _variableMap(variableMap) { - fromJsonHelper(q, base); } //////////////////////////////////////////////////////////////////////////////// @@ -726,10 +702,9 @@ void AggregateNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of ReturnNode // ----------------------------------------------------------------------------- -ReturnNode::ReturnNode (triagens::aql::Query* q, basics::Json const& base) +ReturnNode::ReturnNode (Ast* ast, basics::Json const& base) : ExecutionNode(base), - _inVariable(q->registerVar(new Variable(base.get("inVariable")))) { - fromJsonHelper(q, base); + _inVariable(varFromJson(ast, base, "inVariable")) { } //////////////////////////////////////////////////////////////////////////////// @@ -754,11 +729,11 @@ void ReturnNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- ModificationNode // ----------------------------------------------------------------------------- -ModificationNode::ModificationNode (triagens::aql::Query* q, +ModificationNode::ModificationNode (Ast* ast, basics::Json const& base) : ExecutionNode(base), - _vocbase(q->vocbase()), - _collection(q->collections()->get(JsonHelper::getStringValue(base.json(), "collection", ""))), + _vocbase(ast->query()->vocbase()), + _collection(ast->query()->collections()->add(JsonHelper::getStringValue(base.json(), "collection", ""), TRI_TRANSACTION_WRITE)), _options(base) { TRI_ASSERT(_vocbase != nullptr); TRI_ASSERT(_collection != nullptr); @@ -768,11 +743,10 @@ ModificationNode::ModificationNode (triagens::aql::Query* q, // --SECTION-- methods of RemoveNode // ----------------------------------------------------------------------------- -RemoveNode::RemoveNode (triagens::aql::Query* q, basics::Json const& base) - : ModificationNode(q, base), - _inVariable(q->registerVar(new Variable(base.get("inVariable")))), - _outVariable(base.get("outVariable").isEmpty() ? nullptr : q->registerVar(new Variable(base.get("outVariable")))) { - fromJsonHelper(q, base); +RemoveNode::RemoveNode (Ast* ast, basics::Json const& base) + : ModificationNode(ast, base), + _inVariable(varFromJson(ast, base, "inVariable")), + _outVariable(varFromJson(ast, base, "outVariable", Optional)) { } //////////////////////////////////////////////////////////////////////////////// @@ -805,11 +779,10 @@ void RemoveNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of InsertNode // ----------------------------------------------------------------------------- -InsertNode::InsertNode (triagens::aql::Query* q, basics::Json const& base) - : ModificationNode(q, base), - _inVariable(q->registerVar(new Variable(base.get("inVariable")))), - _outVariable(base.get("outVariable").isEmpty() ? nullptr : q->registerVar(new Variable(base.get("outVariable")))) { - fromJsonHelper(q, base); +InsertNode::InsertNode (Ast* ast, basics::Json const& base) + : ModificationNode(ast, base), + _inVariable(varFromJson(ast, base, "inVariable")), + _outVariable(varFromJson(ast, base, "outVariable", Optional)) { } //////////////////////////////////////////////////////////////////////////////// @@ -842,12 +815,11 @@ void InsertNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of UpdateNode // ----------------------------------------------------------------------------- -UpdateNode::UpdateNode (triagens::aql::Query* q, basics::Json const& base) - : ModificationNode(q, base), - _inDocVariable(q->registerVar(new Variable(base.get("inDocVariable")))), - _inKeyVariable(base.get("inKeyVariable").isEmpty() ? nullptr : q->registerVar(new Variable(base.get("inKeyVariable")))), - _outVariable(base.get("outVariable").isEmpty() ? nullptr : q->registerVar(new Variable(base.get("outVariable")))) { - fromJsonHelper(q, base); +UpdateNode::UpdateNode (Ast* ast, basics::Json const& base) + : ModificationNode(ast, base), + _inDocVariable(varFromJson(ast, base, "inDocVariable")), + _inKeyVariable(varFromJson(ast, base, "inKeyVariable", Optional)), + _outVariable(varFromJson(ast, base, "outVariable", Optional)) { } //////////////////////////////////////////////////////////////////////////////// @@ -885,12 +857,11 @@ void UpdateNode::toJsonHelper (triagens::basics::Json& nodes, // --SECTION-- methods of ReplaceNode // ----------------------------------------------------------------------------- -ReplaceNode::ReplaceNode (triagens::aql::Query* q, basics::Json const& base) - : ModificationNode(q, base), - _inDocVariable(q->registerVar(new Variable(base.get("inDocVariable")))), - _inKeyVariable(base.get("inKeyVariable").isEmpty() ? nullptr : q->registerVar(new Variable(base.get("inKeyVariable")))), - _outVariable(base.get("outVariable").isEmpty() ? nullptr : q->registerVar(new Variable(base.get("outVariable")))) { - fromJsonHelper(q, base); +ReplaceNode::ReplaceNode (Ast* ast, basics::Json const& base) + : ModificationNode(ast, base), + _inDocVariable(varFromJson(ast, base, "inDocVariable")), + _inKeyVariable(varFromJson(ast, base, "inKeyVariable", Optional)), + _outVariable(varFromJson(ast, base, "outVariable", Optional)) { } //////////////////////////////////////////////////////////////////////////////// @@ -924,6 +895,25 @@ void ReplaceNode::toJsonHelper (triagens::basics::Json& nodes, nodes(json); } +// ----------------------------------------------------------------------------- +// --SECTION-- methods of NoResultsNode +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief toJson, for NoResultsNode +//////////////////////////////////////////////////////////////////////////////// + +void NoResultsNode::toJsonHelper (triagens::basics::Json& nodes, + TRI_memory_zone_t* zone) { + Json json(ExecutionNode::toJsonHelperGeneric(nodes, zone)); // call base class method + if (json.isEmpty()) { + return; + } + + // And add it: + nodes(json); +} + // Local Variables: // mode: outline-minor // outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)" diff --git a/arangod/Aql/ExecutionNode.h b/arangod/Aql/ExecutionNode.h index d1966ba609..fc06eaf00a 100644 --- a/arangod/Aql/ExecutionNode.h +++ b/arangod/Aql/ExecutionNode.h @@ -47,8 +47,6 @@ using Json = triagens::basics::Json; - - namespace triagens { namespace aql { @@ -94,25 +92,19 @@ namespace triagens { REMOVE = 23, REPLACE = 24, UPDATE = 25, - RETURN = 26 + RETURN = 26, + NORESULTS = 27 }; // ----------------------------------------------------------------------------- // --SECTION-- constructors / destructors // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @brief factory from json. -//////////////////////////////////////////////////////////////////////////////// - - static ExecutionNode* fromJsonFactory (Ast const* ast, - basics::Json const& json); - //////////////////////////////////////////////////////////////////////////////// /// @brief constructor using an id //////////////////////////////////////////////////////////////////////////////// - ExecutionNode (size_t id) + ExecutionNode (size_t id, double cost = 0.0) : _id(id), _estimatedCost(0), _varUsageValid(false) { @@ -122,9 +114,7 @@ namespace triagens { /// @brief constructor using a JSON struct //////////////////////////////////////////////////////////////////////////////// - ExecutionNode (triagens::basics::Json const& json) - : ExecutionNode(triagens::basics::JsonHelper::getNumericValue(json.json(), "id", 0)) { - } + ExecutionNode (triagens::basics::Json const& json); //////////////////////////////////////////////////////////////////////////////// /// @brief destructor, free dependencies; @@ -139,6 +129,13 @@ namespace triagens { public: +//////////////////////////////////////////////////////////////////////////////// +/// @brief factory from json. +//////////////////////////////////////////////////////////////////////////////// + + static ExecutionNode* fromJsonFactory (Ast* ast, + basics::Json const& json); + //////////////////////////////////////////////////////////////////////////////// /// @brief return the node's id //////////////////////////////////////////////////////////////////////////////// @@ -277,17 +274,6 @@ namespace triagens { triagens::basics::Json toJson (TRI_memory_zone_t* zone = TRI_UNKNOWN_MEM_ZONE); -//////////////////////////////////////////////////////////////////////////////// -/// @brief toJsonHelper, for a generic node -//////////////////////////////////////////////////////////////////////////////// - - void fromJsonHelper (triagens::aql::Query* query, - basics::Json const& base); - - triagens::basics::Json toJsonHelperGeneric ( - triagens::basics::Json& nodes, - TRI_memory_zone_t* zone); - //////////////////////////////////////////////////////////////////////////////// /// @brief toJson //////////////////////////////////////////////////////////////////////////////// @@ -363,6 +349,29 @@ namespace triagens { _varUsageValid = false; } +// ----------------------------------------------------------------------------- +// --SECTION-- protected functions +// ----------------------------------------------------------------------------- + + protected: + +//////////////////////////////////////////////////////////////////////////////// +/// @brief factory for (optional) variables from json. +//////////////////////////////////////////////////////////////////////////////// + + static Variable* varFromJson (Ast*, + triagens::basics::Json const& base, + const char *variableName, + bool optional = false); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief toJsonHelper, for a generic node +//////////////////////////////////////////////////////////////////////////////// + + triagens::basics::Json toJsonHelperGeneric ( + triagens::basics::Json& nodes, + TRI_memory_zone_t* zone); + // ----------------------------------------------------------------------------- // --SECTION-- protected variables // ----------------------------------------------------------------------------- @@ -404,6 +413,7 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// std::unordered_set _varsUsedLater; + std::unordered_set _varsValid; bool _varUsageValid; @@ -424,7 +434,7 @@ namespace triagens { friend class SingletonBlock; //////////////////////////////////////////////////////////////////////////////// -/// @brief constructor with a vocbase and a collection name +/// @brief constructor with an id //////////////////////////////////////////////////////////////////////////////// public: @@ -433,7 +443,7 @@ namespace triagens { : ExecutionNode(id) { } - SingletonNode (triagens::aql::Query* query, basics::Json const& base); + SingletonNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -502,7 +512,7 @@ namespace triagens { TRI_ASSERT(_outVariable != nullptr); } - EnumerateCollectionNode (triagens::aql::Query* Q, basics::Json const& base); + EnumerateCollectionNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -656,7 +666,7 @@ namespace triagens { TRI_ASSERT(_outVariable != nullptr); } - EnumerateListNode (triagens::aql::Query* Q, basics::Json const& base); + EnumerateListNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -1097,7 +1107,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con _limit(limit) { } - LimitNode (triagens::aql::Query* query, basics::Json const& base); + LimitNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -1176,7 +1186,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con TRI_ASSERT(_outVariable != nullptr); } - CalculationNode (triagens::aql::Query* Q, basics::Json const& base); + CalculationNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief destructor @@ -1301,12 +1311,12 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con public: - SubqueryNode (Ast const* ast, - triagens::aql::Query* Q, + SubqueryNode (Ast*, basics::Json const& base); SubqueryNode (size_t id, - ExecutionNode* subquery, Variable const* outVariable) + ExecutionNode* subquery, + Variable const* outVariable) : ExecutionNode(id), _subquery(subquery), _outVariable(outVariable) { @@ -1426,12 +1436,13 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con FilterNode (size_t id, Variable const* inVariable) : ExecutionNode(id), - _inVariable(inVariable) { + _inVariable(inVariable), + _resultIsEmpty(false) { TRI_ASSERT(_inVariable != nullptr); } - FilterNode (triagens::aql::Query* Q, basics::Json const& base); + FilterNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -1458,11 +1469,24 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con return static_cast(c); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief this is a hint that the filter will never let any data pass +//////////////////////////////////////////////////////////////////////////////// + + void setEmptyResult () { + _resultIsEmpty = true; + } + //////////////////////////////////////////////////////////////////////////////// /// @brief the cost of a filter node is . . . FIXME //////////////////////////////////////////////////////////////////////////////// double estimateCost () { + if (_resultIsEmpty) { + // filter will not let any data through + return 0; + } + return _dependencies.at(0)->getCost() * 0.105; //FIXME! 0.005 is the cost of doing the filter node under the //assumption that it returns 10% of the results of its dependency @@ -1486,6 +1510,11 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con Variable const* _inVariable; +//////////////////////////////////////////////////////////////////////////////// +/// @brief hint that is set to true if the filter will not let any data through +//////////////////////////////////////////////////////////////////////////////// + + bool _resultIsEmpty; }; // ----------------------------------------------------------------------------- @@ -1513,7 +1542,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con _elements(elements) { } - SortNode (triagens::aql::Query* query, + SortNode (Ast*, basics::Json const& base, std::vector> elements); @@ -1609,7 +1638,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con // outVariable can be a nullptr } - AggregateNode (triagens::aql::Query* query, + AggregateNode (Ast*, basics::Json const& base, Variable const* outVariable, std::unordered_map const& variableMap, @@ -1730,7 +1759,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con TRI_ASSERT(_inVariable != nullptr); } - ReturnNode (triagens::aql::Query* Q, basics::Json const& base); + ReturnNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -1821,7 +1850,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con TRI_ASSERT(_collection != nullptr); } - ModificationNode (triagens::aql::Query* q, + ModificationNode (Ast*, basics::Json const& json); // ----------------------------------------------------------------------------- @@ -1884,7 +1913,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con // _outVariable might be a nullptr } - RemoveNode (triagens::aql::Query* Q, basics::Json const& base); + RemoveNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -1996,7 +2025,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con // _outVariable might be a nullptr } - InsertNode (triagens::aql::Query* Q, basics::Json const& base); + InsertNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -2110,7 +2139,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con // _outVariable might be a nullptr } - UpdateNode (triagens::aql::Query* Q, basics::Json const& base); + UpdateNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -2234,7 +2263,7 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con // _outVariable might be a nullptr } - ReplaceNode (triagens::aql::Query* Q, basics::Json const& base); + ReplaceNode (Ast*, basics::Json const& base); //////////////////////////////////////////////////////////////////////////////// /// @brief return the type of the node @@ -2322,6 +2351,67 @@ static int CompareRangeInfoBound (RangeInfoBound const* left, RangeInfoBound con }; +// ----------------------------------------------------------------------------- +// --SECTION-- class NoResultsNode +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief class NoResultsNode +//////////////////////////////////////////////////////////////////////////////// + + class NoResultsNode : public ExecutionNode { + + friend class ExecutionBlock; + friend class NoResultsBlock; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief constructor with an id +//////////////////////////////////////////////////////////////////////////////// + + public: + + NoResultsNode (size_t id) + : ExecutionNode(id) { + } + + NoResultsNode (Ast*, basics::Json const& base) + : ExecutionNode(base) { + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the type of the node +//////////////////////////////////////////////////////////////////////////////// + + NodeType getType () const override { + return NORESULTS; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief export to JSON +//////////////////////////////////////////////////////////////////////////////// + + virtual void toJsonHelper (triagens::basics::Json& nodes, + TRI_memory_zone_t* zone = TRI_UNKNOWN_MEM_ZONE); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief clone ExecutionNode recursively +//////////////////////////////////////////////////////////////////////////////// + + virtual ExecutionNode* clone () const { + auto c = new NoResultsNode(_id); + cloneDependencies(c); + return static_cast(c); + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief the cost of a NoResults is 0 +//////////////////////////////////////////////////////////////////////////////// + + double estimateCost () { + return 0; + } + + }; } // namespace triagens::aql } // namespace triagens diff --git a/arangod/Aql/ExecutionPlan.cpp b/arangod/Aql/ExecutionPlan.cpp index 0a006357ae..efc04aba2a 100644 --- a/arangod/Aql/ExecutionPlan.cpp +++ b/arangod/Aql/ExecutionPlan.cpp @@ -75,7 +75,7 @@ ExecutionPlan::~ExecutionPlan () { /// @brief create an execution plan from an AST //////////////////////////////////////////////////////////////////////////////// -ExecutionPlan* ExecutionPlan::instanciateFromAst (Ast const* ast) { +ExecutionPlan* ExecutionPlan::instanciateFromAst (Ast* ast) { TRI_ASSERT(ast != nullptr); auto root = ast->root(); @@ -87,17 +87,19 @@ ExecutionPlan* ExecutionPlan::instanciateFromAst (Ast const* ast) { try { plan->_root = plan->fromNode(ast, root); plan->findVarUsage(); -/* +// just for debugging +/* auto JsonPlan = plan->_root->toJson(); auto JsonString = JsonPlan.toString(); - + std::cout << JsonString << "\n"; auto otherPlan = ExecutionPlan::instanciateFromJson (ast, JsonPlan); auto otherJsonString = otherPlan->_root->toJson().toString(); - /// TRI_ASSERT(otherJsonString == JsonString); - /// JsonHelper + std::cout << otherJsonString << "\n"; + TRI_ASSERT(otherJsonString == JsonString); return otherPlan; */ +// return plan; } catch (...) { @@ -106,12 +108,16 @@ ExecutionPlan* ExecutionPlan::instanciateFromAst (Ast const* ast) { } } -ExecutionPlan* ExecutionPlan::instanciateFromJson (Ast const* ast, - triagens::basics::Json const& Json) { +//////////////////////////////////////////////////////////////////////////////// +/// @brief create an execution plan from JSON +//////////////////////////////////////////////////////////////////////////////// + +ExecutionPlan* ExecutionPlan::instanciateFromJson (Ast* ast, + triagens::basics::Json const& json) { auto plan = new ExecutionPlan(); try { - plan->_root = plan->fromJson(ast, Json); + plan->_root = plan->fromJson(ast, json); plan->findVarUsage(); return plan; } @@ -854,10 +860,13 @@ class NodeFinder : public WalkerWorker { std::vector& _out; + bool _enterSubqueries; + public: NodeFinder (ExecutionNode::NodeType lookingFor, - std::vector& out) - : _lookingFor(lookingFor), _out(out) { + std::vector& out, + bool enterSubqueries) + : _lookingFor(lookingFor), _out(out), _enterSubqueries(enterSubqueries) { }; void before (ExecutionNode* en) { @@ -866,13 +875,17 @@ class NodeFinder : public WalkerWorker { } } + bool enterSubquery (ExecutionNode* super, ExecutionNode* sub) { + return _enterSubqueries; + } }; std::vector ExecutionPlan::findNodesOfType ( - ExecutionNode::NodeType type) { + ExecutionNode::NodeType type, + bool enterSubqueries) { std::vector result; - NodeFinder finder(type, result); + NodeFinder finder(type, result, enterSubqueries); root()->walk(&finder); return result; } @@ -1064,7 +1077,7 @@ ExecutionPlan* ExecutionPlan::clone (){ /// @brief create a plan from the JSON provided //////////////////////////////////////////////////////////////////////////////// -ExecutionNode* ExecutionPlan::fromJson (Ast const* ast, +ExecutionNode* ExecutionPlan::fromJson (Ast* ast, Json const& json) { ExecutionNode* ret = nullptr; Json nodes = json.get("nodes"); diff --git a/arangod/Aql/ExecutionPlan.h b/arangod/Aql/ExecutionPlan.h index fa286f669e..346f796f15 100644 --- a/arangod/Aql/ExecutionPlan.h +++ b/arangod/Aql/ExecutionPlan.h @@ -79,9 +79,13 @@ namespace triagens { /// @brief create an execution plan from an AST //////////////////////////////////////////////////////////////////////////////// - static ExecutionPlan* instanciateFromAst (Ast const*); + static ExecutionPlan* instanciateFromAst (Ast*); - static ExecutionPlan* instanciateFromJson (Ast const* ast, +//////////////////////////////////////////////////////////////////////////////// +/// @brief create an execution plan from JSON +//////////////////////////////////////////////////////////////////////////////// + + static ExecutionPlan* instanciateFromJson (Ast* ast, triagens::basics::Json const& Json); //////////////////////////////////////////////////////////////////////////////// @@ -132,7 +136,8 @@ namespace triagens { /// @brief find nodes of a certain type //////////////////////////////////////////////////////////////////////////////// - std::vector findNodesOfType (ExecutionNode::NodeType); + std::vector findNodesOfType (ExecutionNode::NodeType, + bool enterSubqueries); //////////////////////////////////////////////////////////////////////////////// /// @brief determine and set _varsUsedLater in all nodes @@ -301,8 +306,9 @@ namespace triagens { ExecutionNode* fromNode (Ast const*, AstNode const*); - ExecutionNode* fromJson (Ast const* ast, + ExecutionNode* fromJson (Ast*, triagens::basics::Json const& Json); + // ----------------------------------------------------------------------------- // --SECTION-- private variables // ----------------------------------------------------------------------------- diff --git a/arangod/Aql/Expression.cpp b/arangod/Aql/Expression.cpp index 33baa2680c..bcabf5fc73 100644 --- a/arangod/Aql/Expression.cpp +++ b/arangod/Aql/Expression.cpp @@ -65,10 +65,14 @@ Expression::Expression (V8Executor* executor, analyzeExpression(); } -Expression::Expression (triagens::aql::Query* Q, +//////////////////////////////////////////////////////////////////////////////// +/// @brief create an expression from JSON +//////////////////////////////////////////////////////////////////////////////// + +Expression::Expression (Ast* ast, triagens::basics::Json const& json) - : _executor(Q->executor()), - _node(Q->registerNode(new AstNode(Q, json.get("expression")))), + : _executor(ast->query()->executor()), + _node(new AstNode(ast, json.get("expression"))), _type(UNPROCESSED) { TRI_ASSERT(_executor != nullptr); @@ -189,6 +193,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, std::vector const& regs) { if (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) { + // array lookup, e.g. users.name TRI_ASSERT(node->numMembers() == 1); auto member = node->getMember(0); @@ -200,8 +205,14 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, auto j = result.extractArrayMember(trx, myCollection, name); return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); } -/* + else if (node->type == NODE_TYPE_INDEXED_ACCESS) { + // list lookup, e.g. users[0] + // note: it depends on the type of the value whether a list lookup or an array lookup is performed + // for example, if the value is an array, then its elements might be access like this: + // users['name'] or even users['0'] (as '0' is a valid attribute name, too) + // if the value is a list, then string indexes might also be used and will be converted to integers, e.g. + // users['0'] is the same as users[0], users['-2'] is the same as users[-2] etc. TRI_ASSERT(node->numMembers() == 2); auto member = node->getMember(0); @@ -210,10 +221,46 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, TRI_document_collection_t const* myCollection = nullptr; AqlValue result = executeSimpleExpression(member, &myCollection, trx, docColls, argv, startPos, vars, regs); - auto j = result.extractListMember(trx, myCollection, name); - return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); + if (result.isList()) { + if (index->isNumericValue()) { + auto j = result.extractListMember(trx, myCollection, index->getIntValue()); + return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); + } + else if (index->isStringValue()) { + char const* p = index->getStringValue(); + TRI_ASSERT(p != nullptr); + + try { + // stoll() might throw an exception if the string is not a number + int64_t position = static_cast(std::stoll(p)); + auto j = result.extractListMember(trx, myCollection, position); + return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); + } + catch (...) { + // no number found. + } + } + // fall-through to returning null + } + else if (result.isArray()) { + if (index->isNumericValue()) { + std::string const indexString = std::to_string(index->getIntValue()); + auto j = result.extractArrayMember(trx, myCollection, indexString.c_str()); + return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); + } + else if (index->isStringValue()) { + char const* p = index->getStringValue(); + TRI_ASSERT(p != nullptr); + + auto j = result.extractArrayMember(trx, myCollection, p); + return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal())); + } + // fall-through to returning null + } + + return AqlValue(new Json(Json::Null)); } - */ + else if (node->type == NODE_TYPE_LIST) { auto list = new Json(Json::List); @@ -273,6 +320,16 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, } // fall-through to exception } + + else if (node->type == NODE_TYPE_VALUE) { + auto j = node->toJsonValue(TRI_UNKNOWN_MEM_ZONE); + + if (j == nullptr) { + THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); + } + + return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j)); + } THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unhandled type in simple expression"); } diff --git a/arangod/Aql/Expression.h b/arangod/Aql/Expression.h index 52c4822567..e1909365d1 100644 --- a/arangod/Aql/Expression.h +++ b/arangod/Aql/Expression.h @@ -46,6 +46,7 @@ namespace triagens { class AqlItemBlock; struct AqlValue; + class Ast; struct Variable; class V8Executor; struct V8Expression; @@ -76,7 +77,7 @@ namespace triagens { Expression (V8Executor*, AstNode const*); - Expression (triagens::aql::Query* Q, + Expression (Ast*, triagens::basics::Json const &json); //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Optimizer.cpp b/arangod/Aql/Optimizer.cpp index 833e54d0fd..ae2a4f4d86 100644 --- a/arangod/Aql/Optimizer.cpp +++ b/arangod/Aql/Optimizer.cpp @@ -42,18 +42,19 @@ Optimizer::Optimizer () { // List all the rules in the system here: // try to find a filter after an enumerate collection and find an index . . . - registerRule (useIndexRange, 999); + // registerRule (useIndexRange, 999); // remove filters from the query that are not necessary at all - // rule should be executed relatively early because it enables removal - // of then-unused filter variables - //registerRule(removeUnnecessaryFiltersRule, 10000); + // filters that are always true will be removed entirely + // filters that are always false will get a hint + registerRule(removeUnnecessaryFiltersRule, 10000); // move calculations up the dependency chain (to pull them out of inner loops etc.) // TODO: validate if this is really an optimization // registerRule(moveCalculationsUpRule, 1000); - //registerRule(removeUnnecessaryCalculationsRule, 999); + // remove calculations that are never necessary + registerRule(removeUnnecessaryCalculationsRule, 999); // Now sort them by pass: std::stable_sort(_rules.begin(), _rules.end()); @@ -95,6 +96,9 @@ int Optimizer::createPlans (ExecutionPlan* plan) { while (oldPlans.size() > 0) { auto p = oldPlans.pop_front(); try { + // keep should have a default value so rules that forget to set it + // have a deterministic behavior + keep = true; res = r.func(this, p, newPlans, keep); if (keep) { nextOldPlans.push_back(p); diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index 3ebef4ef83..eadd36663d 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -37,12 +37,11 @@ using Json = triagens::basics::Json; // --SECTION-- rules for the optimizer // ----------------------------------------------------------------------------- - //////////////////////////////////////////////////////////////////////////////// /// @brief remove all unnecessary filters -/// filters that are always true are removed -/// filters that are always false will be removed plus their dependent nodes -/// this modifies the plan in place +/// this rule modifies the plan in place: +/// - filters that are always true are removed completely +/// - filters that are always false will be removed plus their dependent nodes //////////////////////////////////////////////////////////////////////////////// int triagens::aql::removeUnnecessaryFiltersRule (Optimizer* opt, @@ -50,12 +49,12 @@ int triagens::aql::removeUnnecessaryFiltersRule (Optimizer* opt, Optimizer::PlanList& out, bool& keep) { - keep = true; - std::unordered_set toRemove; - std::vector nodes = plan->findNodesOfType(triagens::aql::ExecutionNode::FILTER); + keep = true; // plan will always be kept + std::unordered_set toUnlink; + std::vector nodes = plan->findNodesOfType(triagens::aql::ExecutionNode::FILTER, true); for (auto n : nodes) { - // filter has one input variable + // filter nodes always have one input variable auto varsUsedHere = n->getVariablesUsedHere(); TRI_ASSERT(varsUsedHere.size() == 1); @@ -63,74 +62,90 @@ int triagens::aql::removeUnnecessaryFiltersRule (Optimizer* opt, auto variable = varsUsedHere[0]; auto setter = plan->getVarSetBy(variable->id); - if (setter != nullptr && - setter->getType() == triagens::aql::ExecutionNode::CALCULATION) { - // if it was a CalculationNode, check its expression - auto s = static_cast(setter); - auto root = s->expression()->node(); + if (setter == nullptr || + setter->getType() != triagens::aql::ExecutionNode::CALCULATION) { + // filter variable was not introduced by a calculation. + continue; + } - if (root->isConstant()) { - // the expression is a constant value - if (root->toBoolean()) { - // filter is always true - // remove filter node and merge with following node - toRemove.insert(n); + // filter variable was introduced a CalculationNode. now check the expression + auto s = static_cast(setter); + auto root = s->expression()->node(); + + if (! root->isConstant()) { + // filter expression can only be evaluated at runtime + continue; + } + + // filter expression is constant and thus cannot throw + // we can now evaluate it safely + TRI_ASSERT(! s->expression()->canThrow()); + + if (root->toBoolean()) { + // filter is always true + // remove filter node and merge with following node + toUnlink.insert(n); + } + else { + // filter is always false + + + // get all dependent nodes of the filter node + std::vector stack; + stack.push_back(n); + + bool canOptimize = true; + + while (! stack.empty()) { + // pop a node from the stack + auto current = stack.back(); + stack.pop_back(); + + if (toUnlink.find(current) != toUnlink.end()) { + // detected a cycle. TODO: decide whether a cycle is an error here or + // if it is valid + break; } - else { - // filter is always false - /* TODO - // get all dependent nodes of the filter node - std::vector stack; - stack.push_back(n); - while (! stack.empty()) { - // pop a node from the stack - auto current = stack.back(); - stack.pop_back(); + if (current->getType() == triagens::aql::ExecutionNode::SINGLETON) { + // stop at a singleton node + break; + } + + if (current->getType() == triagens::aql::ExecutionNode::SUBQUERY) { + // if we find a subquery, we abort optimizations. + // TODO: peek into the subquery and check if it can throw an exception itself + canOptimize = false; + break; + } - bool unlinkNode = true; - - if (toRemove.find(current) != toRemove.end()) { - // detected a cycle. TODO: decide whether a cycle is an error here or - // if it is valid - break; - } - - if (current->getType() == triagens::aql::ExecutionNode::SINGLETON) { - // stop at a singleton node - break; - } - - if (current->getType() == triagens::aql::ExecutionNode::CALCULATION) { - auto c = static_cast(current); - if (c->expression()->node()->canThrow()) { - // the calculation may throw an exception. we must not remove it - // because its removal might change the query result - unlinkNode = false; - std::cout << "FOUND A CALCULATION THAT CAN THROW\n"; - } - } - - auto deps = current->getDependencies(); - for (auto it = deps.begin(); it != deps.end(); ++it) { - stack.push_back((*it)); - } - - if (unlinkNode) { - std::cout << "REMOVING NODE " << current << " OF TYPE: " << current->getTypeString() << "\n"; - toRemove.insert(current); - } + if (current->getType() == triagens::aql::ExecutionNode::CALCULATION) { + auto c = static_cast(current); + if (c->expression()->node()->canThrow()) { + // the calculation may throw an exception. we must not remove it + // because its removal might change the query result + canOptimize = false; + break; } - */ } + + auto deps = current->getDependencies(); + for (auto it = deps.begin(); it != deps.end(); ++it) { + stack.push_back((*it)); + } + } + + if (canOptimize) { + // store a hint in the filter that it will never produce results + static_cast(n)->setEmptyResult(); } } } - if (! toRemove.empty()) { - std::cout << "Removing " << toRemove.size() << " unnecessary " + if (! toUnlink.empty()) { + std::cout << "Removing " << toUnlink.size() << " unnecessary " "nodes..." << std::endl; - plan->unlinkNodes(toRemove); + plan->unlinkNodes(toUnlink); } return TRI_ERROR_NO_ERROR; @@ -145,7 +160,7 @@ int triagens::aql::moveCalculationsUpRule (Optimizer* opt, ExecutionPlan* plan, Optimizer::PlanList& out, bool& keep) { - std::vector nodes = plan->findNodesOfType(triagens::aql::ExecutionNode::CALCULATION); + std::vector nodes = plan->findNodesOfType(triagens::aql::ExecutionNode::CALCULATION, true); for (auto n : nodes) { auto nn = static_cast(n); @@ -159,6 +174,10 @@ int triagens::aql::moveCalculationsUpRule (Optimizer* opt, // (sorting is needed for intersection later) std::sort(neededVars.begin(), neededVars.end(), &Variable::Comparator); +for (auto it = neededVars.begin(); it != neededVars.end(); ++it) { +std::cout << "VAR USED IN CALC: " << (*it)->name << "\n"; +} + std::vector stack; auto deps = n->getDependencies(); @@ -170,6 +189,7 @@ int triagens::aql::moveCalculationsUpRule (Optimizer* opt, auto current = stack.back(); stack.pop_back(); +std::cout << "LOOKING AT NODE OF TYPE: " << current->getTypeString() << "\n"; auto deps = current->getDependencies(); if (deps.size() != 1) { @@ -225,17 +245,20 @@ int triagens::aql::moveCalculationsUpRule (Optimizer* opt, //////////////////////////////////////////////////////////////////////////////// /// @brief remove CalculationNode(s) that are never needed +/// this modifies an existing plan in place //////////////////////////////////////////////////////////////////////////////// int triagens::aql::removeUnnecessaryCalculationsRule (Optimizer* opt, ExecutionPlan* plan, Optimizer::PlanList& out, bool& keep) { + keep = true; std::vector nodes - = plan->findNodesOfType(triagens::aql::ExecutionNode::CALCULATION); - std::unordered_set toRemove; + = plan->findNodesOfType(triagens::aql::ExecutionNode::CALCULATION, true); + std::unordered_set toUnlink; for (auto n : nodes) { auto nn = static_cast(n); + if (nn->expression()->canThrow()) { // If this node can throw, we must not optimize it away! continue; @@ -249,20 +272,16 @@ int triagens::aql::removeUnnecessaryCalculationsRule (Optimizer* opt, // The variable whose value is calculated here is not used at // all further down the pipeline! We remove the whole // calculation node, - toRemove.insert(n); + toUnlink.insert(n); } } - if (! toRemove.empty()) { - std::cout << "Removing " << toRemove.size() << " unnecessary " + if (! toUnlink.empty()) { + std::cout << "Removing " << toUnlink.size() << " unnecessary " "CalculationNodes..." << std::endl; - plan->unlinkNodes(toRemove); - out.push_back(plan); - keep = false; - } - else { - keep = true; + plan->unlinkNodes(toUnlink); } + return TRI_ERROR_NO_ERROR; } @@ -460,7 +479,7 @@ int triagens::aql::useIndexRange (Optimizer* opt, bool& keep) { keep = true; std::vector nodes - = plan->findNodesOfType(triagens::aql::ExecutionNode::FILTER); + = plan->findNodesOfType(triagens::aql::ExecutionNode::FILTER, true); for (auto n : nodes) { auto nn = static_cast(n); diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index 8613640311..939d8d71c9 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -61,6 +61,7 @@ Query::Query (TRI_vocbase_t* vocbase, _executor(nullptr), _queryString(queryString), _queryLength(queryLength), + _queryJson(), _type(AQL_QUERY_READ), _bindParameters(bindParameters), _collections(vocbase), @@ -71,6 +72,24 @@ Query::Query (TRI_vocbase_t* vocbase, _strings.reserve(32); } +Query::Query (TRI_vocbase_t* vocbase, + triagens::basics::Json queryStruct, + QueryType Type) + : _vocbase(vocbase), + _executor(nullptr), + _queryString(nullptr), + _queryLength(0), + _queryJson(queryStruct), + _type(Type), + _bindParameters(nullptr), + _collections(vocbase), + _strings() { + + TRI_ASSERT(_vocbase != nullptr); + + _strings.reserve(32); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief destroys a query //////////////////////////////////////////////////////////////////////////////// @@ -174,24 +193,53 @@ void Query::registerError (int code, QueryResult Query::execute () { try { + ExecutionPlan* plan; Parser parser(this); - parser.parse(); - // put in bind parameters - parser.ast()->injectBindParameters(_bindParameters); - // optimize the ast - parser.ast()->optimize(); - // std::cout << "AST: " << triagens::basics::JsonHelper::toString(parser.ast()->toJson(TRI_UNKNOWN_MEM_ZONE)) << "\n"; - - triagens::arango::AqlTransaction> trx(_vocbase, _collections.collections()); - - int res = trx.begin(); - - if (res != TRI_ERROR_NO_ERROR) { - return QueryResult(res, TRI_errno_string(res)); + if (_queryString != nullptr) { + parser.parse(); + // put in bind parameters + parser.ast()->injectBindParameters(_bindParameters); + // optimize the ast + parser.ast()->optimize(); + // std::cout << "AST: " << triagens::basics::JsonHelper::toString(parser.ast()->toJson(TRI_UNKNOWN_MEM_ZONE)) << "\n"; } - auto plan = ExecutionPlan::instanciateFromAst(parser.ast()); + // create the transaction object, but do not start it yet + AQL_TRANSACTION_V8 trx(_vocbase, _collections.collections()); + + if (_queryString != nullptr) { + // we have an AST + int res = trx.begin(); + + if (res != TRI_ERROR_NO_ERROR) { + return QueryResult(res, TRI_errno_string(res)); + } + + plan = ExecutionPlan::instanciateFromAst(parser.ast()); + if (plan == nullptr) { + // oops + return QueryResult(TRI_ERROR_INTERNAL); + } + } + else { + // we have an execution plan in JSON format + plan = ExecutionPlan::instanciateFromJson(parser.ast(), _queryJson); + if (plan == nullptr) { + // oops + return QueryResult(TRI_ERROR_INTERNAL); + } + + // creating the plan may have produced some collections + // we need to add them to the transaction now (otherwise the query will fail) + trx.addCollections(_collections.collections()); + + int res = trx.begin(); + + if (res != TRI_ERROR_NO_ERROR) { + return QueryResult(res, TRI_errno_string(res)); + } + } // Run the query optimiser: triagens::aql::Optimizer opt; @@ -333,10 +381,15 @@ char* Query::registerString (char const* p, return copy; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief register a string +/// the string is freed when the query is destroyed +//////////////////////////////////////////////////////////////////////////////// + char* Query::registerString (std::string const& p, bool mustUnescape) { - return registerString (p.c_str(),p.length(), mustUnescape); + return registerString(p.c_str(), p.length(), mustUnescape); } // ----------------------------------------------------------------------------- diff --git a/arangod/Aql/Query.h b/arangod/Aql/Query.h index 735a2e8571..1bcea290e7 100644 --- a/arangod/Aql/Query.h +++ b/arangod/Aql/Query.h @@ -31,6 +31,7 @@ #define ARANGODB_AQL_QUERY_H 1 #include "Basics/Common.h" +#include "Basics/JsonHelper.h" #include "Aql/BindParameters.h" #include "Aql/Collections.h" #include "Aql/QueryResult.h" @@ -45,6 +46,7 @@ namespace triagens { class Expression; struct Variable; struct AstNode; + // ----------------------------------------------------------------------------- // --SECTION-- public types // ----------------------------------------------------------------------------- @@ -82,6 +84,10 @@ namespace triagens { size_t, struct TRI_json_s*); + Query (struct TRI_vocbase_s*, + triagens::basics::Json queryStruct, + QueryType Type); + ~Query (); // ----------------------------------------------------------------------------- @@ -193,45 +199,14 @@ namespace triagens { size_t, bool); +//////////////////////////////////////////////////////////////////////////////// +/// @brief register a string +/// the string is freed when the query is destroyed +//////////////////////////////////////////////////////////////////////////////// + char* registerString (std::string const&, bool); -//////////////////////////////////////////////////////////////////////////////// -/// @brief register an Expression -/// the Expression is freed when the query is destroyed -//////////////////////////////////////////////////////////////////////////////// - - Expression* registerExp (Expression* x) { - _expressions.push_back(x); - return x; - } - -//////////////////////////////////////////////////////////////////////////////// -/// @brief register an AstNode -/// the AstNode is freed when the query is destroyed -//////////////////////////////////////////////////////////////////////////////// - - AstNode* registerNode (AstNode* a) { - _nodes.push_back(a); - return a; - } - -//////////////////////////////////////////////////////////////////////////////// -/// @brief register a Variable -/// the Variable is freed when the query is destroyed -//////////////////////////////////////////////////////////////////////////////// - - Variable* registerVar (Variable* v) { - _variables.push_back(v); - return v; - } - -// ----------------------------------------------------------------------------- -// --SECTION-- private methods -// ----------------------------------------------------------------------------- - - private: - // ----------------------------------------------------------------------------- // --SECTION-- private variables // ----------------------------------------------------------------------------- @@ -262,6 +237,13 @@ namespace triagens { size_t const _queryLength; +//////////////////////////////////////////////////////////////////////////////// +/// @brief query in a JSON structure +//////////////////////////////////////////////////////////////////////////////// + + triagens::basics::Json const _queryJson; + + //////////////////////////////////////////////////////////////////////////////// /// @brief type of the query //////////////////////////////////////////////////////////////////////////////// @@ -285,12 +267,6 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// std::vector _strings; - - std::vector _expressions; - - std::vector _nodes; - - std::vector _variables; }; } diff --git a/arangod/Aql/Types.h b/arangod/Aql/Types.h index 099eb96f3e..156ec1229d 100644 --- a/arangod/Aql/Types.h +++ b/arangod/Aql/Types.h @@ -58,6 +58,16 @@ namespace triagens { // e.g. 10..1 return _low - _high + 1; } + + int64_t at (size_t position) const { + if (_low <= _high) { + // e.g. 1..1, 1..10 etc. + return _low + static_cast(position); + } + + // e.g. 10..1 + return _low - static_cast(position); + } int64_t const _low; int64_t const _high; diff --git a/arangod/Aql/Variable.cpp b/arangod/Aql/Variable.cpp index 6cfb072fe5..5daa9565fa 100644 --- a/arangod/Aql/Variable.cpp +++ b/arangod/Aql/Variable.cpp @@ -29,6 +29,7 @@ #include "Aql/Variable.h" #include "Basics/JsonHelper.h" + using namespace triagens::aql; using namespace triagens::basics; using Json = triagens::basics::Json; @@ -45,16 +46,11 @@ Variable::Variable (std::string const& name, VariableId id) : name(name), value(nullptr), - id(id), - refCount(0) { + id(id) { } - Variable::Variable (Json const& json) - : name(JsonHelper::getStringValue(json.json(), "name", "")), - value(nullptr), - id(JsonHelper::getNumericValue(json.json(), "id", 0.0)), - refCount(0) { + : Variable(JsonHelper::getStringValue(json.json(), "name", ""), JsonHelper::getNumericValue(json.json(), "id", 0)) { } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Variable.h b/arangod/Aql/Variable.h index 40a3bbf3e7..5e74b5c6fc 100644 --- a/arangod/Aql/Variable.h +++ b/arangod/Aql/Variable.h @@ -53,7 +53,6 @@ namespace triagens { Variable (basics::Json const& json); - //////////////////////////////////////////////////////////////////////////////// /// @brief destroy the variable //////////////////////////////////////////////////////////////////////////////// @@ -81,31 +80,6 @@ namespace triagens { return value; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief whether or not the variable is reference counted -//////////////////////////////////////////////////////////////////////////////// - - inline bool isReferenceCounted () const { - return (refCount > 0); - } - -//////////////////////////////////////////////////////////////////////////////// -/// @brief increase the variable's reference counter -//////////////////////////////////////////////////////////////////////////////// - - inline void increaseReferenceCount () { - ++refCount; - } - -//////////////////////////////////////////////////////////////////////////////// -/// @brief decrease the variable's reference counter -//////////////////////////////////////////////////////////////////////////////// - - inline void decreaseReferenceCount () { - TRI_ASSERT(refCount > 0); - --refCount; - } - //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the variable is user-defined //////////////////////////////////////////////////////////////////////////////// @@ -162,12 +136,6 @@ namespace triagens { VariableId const id; -//////////////////////////////////////////////////////////////////////////////// -/// @brief number of times the variable is used in the AST -//////////////////////////////////////////////////////////////////////////////// - - uint32_t refCount; - }; } diff --git a/arangod/Aql/VariableGenerator.cpp b/arangod/Aql/VariableGenerator.cpp index a1500bdd17..0bf7ace234 100644 --- a/arangod/Aql/VariableGenerator.cpp +++ b/arangod/Aql/VariableGenerator.cpp @@ -128,6 +128,32 @@ Variable* VariableGenerator::createVariable (std::string const& name, return variable; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief generate a variable from JSON +//////////////////////////////////////////////////////////////////////////////// + +Variable* VariableGenerator::createVariable (triagens::basics::Json const& json) { + auto variable = new Variable(json); + + auto existing = getVariable(variable->id); + if (existing != nullptr) { + // variable already existed. + delete variable; + return existing; + } + + try { + _variables.insert(std::make_pair(variable->id, variable)); + } + catch (...) { + // prevent memleak + delete variable; + throw; + } + + return variable; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief generate a temporary variable //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/VariableGenerator.h b/arangod/Aql/VariableGenerator.h index 65c4975fe8..72c9be6b2c 100644 --- a/arangod/Aql/VariableGenerator.h +++ b/arangod/Aql/VariableGenerator.h @@ -31,6 +31,7 @@ #define ARANGODB_AQL_VARIABLE_GENERATOR_H 1 #include "Basics/Common.h" +#include "Basics/JsonHelper.h" #include "Aql/Variable.h" namespace triagens { @@ -86,6 +87,12 @@ namespace triagens { Variable* createVariable (std::string const&, bool); +//////////////////////////////////////////////////////////////////////////////// +/// @brief generate a variable from JSON +//////////////////////////////////////////////////////////////////////////////// + + Variable* createVariable (triagens::basics::Json const&); + //////////////////////////////////////////////////////////////////////////////// /// @brief generate a temporary variable //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Utils/AqlTransaction.h b/arangod/Utils/AqlTransaction.h index d4ca86a9c6..99b666f92c 100644 --- a/arangod/Utils/AqlTransaction.h +++ b/arangod/Utils/AqlTransaction.h @@ -81,6 +81,12 @@ namespace triagens { ~AqlTransaction () { } + void addCollections (std::map* collections) { + for (auto it = collections->begin(); it != collections->end(); ++it) { + processCollection((*it).second); + } + } + //////////////////////////////////////////////////////////////////////////////// /// @brief add a collection to the transaction //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index 11044db899..ba77a915de 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -861,6 +861,117 @@ static v8::Handle JS_ParseAql (v8::Arguments const& argv) { /// @brief executes an AQL query //////////////////////////////////////////////////////////////////////////////// +static v8::Handle JS_ExecuteAqlJson (v8::Arguments const& argv) { + v8::HandleScope scope; + + TRI_vocbase_t* vocbase = GetContextVocBase(); + + if (vocbase == nullptr) { + TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); + } + + if (argv.Length() < 1 || argv.Length() > 2) { + TRI_V8_EXCEPTION_USAGE(scope, "AQL_EXECUTEJSON(, )"); + } + + // return number of total records in cursor? + bool doCount = false; + + // maximum number of results to return at once + size_t batchSize = SIZE_MAX; + + // ttl for cursor + double ttl = 0.0; + + // extra return values + TRI_json_t* extra = nullptr; + + if (! argv[0]->IsObject()) { + TRI_V8_TYPE_ERROR(scope, "expecting object for "); + } + TRI_json_t* queryjson = TRI_ObjectToJson(argv[0]); + + if (argv.Length() > 1) { + // we have options! yikes! + if (! argv[1]->IsUndefined() && ! argv[1]->IsObject()) { + TRI_V8_TYPE_ERROR(scope, "expecting object for "); + } + + v8::Handle argValue = v8::Handle::Cast(argv[1]); + + v8::Handle optionName = v8::String::New("batchSize"); + if (argValue->Has(optionName)) { + batchSize = static_cast(TRI_ObjectToInt64(argValue->Get(optionName))); + if (batchSize == 0) { + TRI_V8_TYPE_ERROR(scope, "expecting non-zero value for "); + // well, this makes no sense + } + } + + optionName = v8::String::New("count"); + if (argValue->Has(optionName)) { + doCount = TRI_ObjectToBoolean(argValue->Get(optionName)); + } + + optionName = v8::String::New("ttl"); + if (argValue->Has(optionName)) { + ttl = TRI_ObjectToBoolean(argValue->Get(optionName)); + ttl = (ttl <= 0.0 ? 30.0 : ttl); + } + + optionName = v8::String::New("extra"); + if (argValue->Has(optionName)) { + extra = TRI_ObjectToJson(argValue->Get(optionName)); + } + } + + triagens::aql::Query query(vocbase, Json(TRI_UNKNOWN_MEM_ZONE, queryjson), triagens::aql::AQL_QUERY_READ); + + auto queryResult = query.execute(); + + if (queryResult.code != TRI_ERROR_NO_ERROR) { + TRI_V8_EXCEPTION_FULL(scope, queryResult.code, queryResult.details); + } + + if (TRI_LengthListJson(queryResult.json) <= batchSize) { + // return the array value as it is. this is a performance optimisation + v8::Handle result = v8::Object::New(); + if (queryResult.json != nullptr) { + result->Set(TRI_V8_STRING("json"), TRI_ObjectJson(queryResult.json)); + } + return scope.Close(result); + } + + TRI_general_cursor_result_t* cursorResult = TRI_CreateResultAql(queryResult.json); + + if (cursorResult == nullptr){ + TRI_V8_EXCEPTION_MEMORY(scope); + } + + queryResult.json = nullptr; + + TRI_general_cursor_t* cursor = TRI_CreateGeneralCursor(vocbase, cursorResult, doCount, + batchSize, ttl, extra); + + if (cursor == nullptr) { + TRI_Free(TRI_UNKNOWN_MEM_ZONE, cursorResult); + TRI_V8_EXCEPTION_MEMORY(scope); + } + TRI_ASSERT(cursor != nullptr); + + v8::Handle cursorObject = TRI_WrapGeneralCursor(cursor); + + if (cursorObject.IsEmpty()) { + TRI_V8_EXCEPTION_MEMORY(scope); + } + + return scope.Close(cursorObject); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes an AQL query +//////////////////////////////////////////////////////////////////////////////// + static v8::Handle JS_ExecuteAql (v8::Arguments const& argv) { v8::HandleScope scope; @@ -981,6 +1092,7 @@ static v8::Handle JS_ExecuteAql (v8::Arguments const& argv) { return scope.Close(cursorObject); } + // ----------------------------------------------------------------------------- // --SECTION-- AHUACATL // ----------------------------------------------------------------------------- @@ -2560,6 +2672,7 @@ void TRI_InitV8VocBridge (v8::Handle context, // new AQL functions. not intended to be used directly by end users TRI_AddGlobalFunctionVocbase(context, "AQL_EXECUTE", JS_ExecuteAql, true); + TRI_AddGlobalFunctionVocbase(context, "AQL_EXECUTEJSON", JS_ExecuteAqlJson, true); TRI_AddGlobalFunctionVocbase(context, "AQL_PARSE", JS_ParseAql, true); TRI_InitV8replication(context, server, vocbase, loader, threadNumber, v8g); diff --git a/js/actions/api-aql2.js b/js/actions/api-aql2.js new file mode 100644 index 0000000000..a89bf9ed07 --- /dev/null +++ b/js/actions/api-aql2.js @@ -0,0 +1,73 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require,AQL_EXECUTEJSON */ + +//////////////////////////////////////////////////////////////////////////////// +/// @brief AQL user functions management +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2014 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Jan Steemann +/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany +/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +var actions = require("org/arangodb/actions"); + +function post_api_executeJson (req, res) { + var json = actions.getJsonBody(req, res, actions.HTTP_BAD); + + if (json === undefined) { + return; + } + + var result = AQL_EXECUTEJSON(json.tree, json.options); + + actions.resultOk(req, res, actions.HTTP_OK, result); +} + +actions.defineHttp({ + url : "_api/aql2/execute-json", + + callback : function (req, res) { + try { + switch (req.requestType) { + case actions.POST: + post_api_executeJson(req, res); + break; + + default: + actions.resultUnsupported(req, res); + } + } + catch (err) { + actions.resultException(req, res, err, undefined, false); + } + } +}); + +// ----------------------------------------------------------------------------- +// --SECTION-- END-OF-FILE +// ----------------------------------------------------------------------------- + +// Local Variables: +// mode: outline-minor +// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}" +// End: diff --git a/lib/Basics/JsonHelper.h b/lib/Basics/JsonHelper.h index 492a9b1e96..607e80e204 100644 --- a/lib/Basics/JsonHelper.h +++ b/lib/Basics/JsonHelper.h @@ -486,6 +486,8 @@ namespace triagens { : _zone(z), _json(j), _autofree(autofree) { } + explicit Json (TRI_json_t* j) = delete; + //////////////////////////////////////////////////////////////////////////////// /// @brief "copy" constructor, note that in the AUTOFREE case this steals /// the structure from j to allow returning Json objects by value without diff --git a/scripts/jsonQueryToYaml b/scripts/jsonQueryToYaml new file mode 100755 index 0000000000..caa9d7af47 --- /dev/null +++ b/scripts/jsonQueryToYaml @@ -0,0 +1,59 @@ +#!/usr/bin/python + +import json +import sys +import yaml + +fn='' +if len(sys.argv) > 1: + fn = sys.argv[1] +if len(fn) > 0: + infile = open(fn, 'r') +else: + infile = sys.stdin + +raw=infile.read() + +#print raw +instruct = json.loads(raw) + +blacklist = ['typeID', 'vTypeID', 'varsUsedLater', 'varsValid'] + +def Filter(instruct, layer): + + if type(instruct) == dict: + out = {} + #print "<- %s %d\n" % (instruct, layer) + for oneKey in sorted(instruct): #.keys(): + if not oneKey in blacklist: + if type(instruct[oneKey]) == dict: + out[oneKey] = Filter(instruct[oneKey], layer + 1) + elif type(instruct[oneKey]) == list: + out[oneKey] = Filter(instruct[oneKey], layer + 1) + else: + out[oneKey] = instruct[oneKey] + #else: + #print "BLACKLIST %d\n" % (layer) + #print "-> %s %d\n" % (out, layer) + return out + elif type(instruct) == list: + out = [] + for item in instruct: + if type(item) == dict: + out.append(Filter(item, layer + 1)) + elif type(item) == list: + out.append(Filter(item, layer + 1)) + else: + out.append(item) + return out + else: + return instruct + + + + +filtered = Filter(instruct, 0) +#print "---------" +#print filtered +# print json.dumps(instruct, indent=4, sort_keys=True) +print yaml.safe_dump(filtered, default_flow_style=False)