//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// #include "Aql/AstNode.h" #include "Aql/Ast.h" #include "Aql/Executor.h" #include "Aql/Function.h" #include "Aql/Quantifier.h" #include "Aql/Query.h" #include "Aql/Scopes.h" #include "Aql/types.h" #include "Basics/fasthash.h" #include "Basics/JsonHelper.h" #include "Basics/json-utilities.h" #include "Basics/StringBuffer.h" #include "Basics/Utf8Helper.h" #ifdef ARANGODB_ENABLE_MAINTAINER_MODE #include #endif #include #include #include #include using namespace arangodb::aql; using JsonHelper = arangodb::basics::JsonHelper; using Json = arangodb::basics::Json; /// @brief quick translation array from an AST node value type to a JSON type std::array const JsonTypes{{ TRI_JSON_NULL, // VALUE_TYPE_NULL = 0, TRI_JSON_BOOLEAN, // VALUE_TYPE_BOOL = 1, TRI_JSON_NUMBER, // VALUE_TYPE_INT = 2, TRI_JSON_NUMBER, // VALUE_TYPE_DOUBLE = 3, TRI_JSON_STRING // VALUE_TYPE_STRING = 4 }}; static_assert(AstNodeValueType::VALUE_TYPE_NULL == 0, "incorrect ast node value types"); static_assert(AstNodeValueType::VALUE_TYPE_BOOL == 1, "incorrect ast node value types"); static_assert(AstNodeValueType::VALUE_TYPE_INT == 2, "incorrect ast node value types"); static_assert(AstNodeValueType::VALUE_TYPE_DOUBLE == 3, "incorrect ast node value types"); static_assert(AstNodeValueType::VALUE_TYPE_STRING == 4, "incorrect ast node value types"); std::unordered_map const AstNode::Operators{ {static_cast(NODE_TYPE_OPERATOR_UNARY_NOT), "!"}, {static_cast(NODE_TYPE_OPERATOR_UNARY_PLUS), "+"}, {static_cast(NODE_TYPE_OPERATOR_UNARY_MINUS), "-"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_AND), "&&"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_OR), "||"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_PLUS), "+"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_MINUS), "-"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_TIMES), "*"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_DIV), "/"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_MOD), "%"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_EQ), "=="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_NE), "!="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_LT), "<"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_LE), "<="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_GT), ">"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_GE), ">="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_IN), "IN"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_NIN), "NOT IN"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ), "array =="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_NE), "array !="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_LT), "array <"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_LE), "array <="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_GT), "array >"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_GE), "array >="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_IN), "array IN"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN), "array NOT IN"}}; /// @brief type names for AST nodes std::unordered_map const AstNode::TypeNames{ {static_cast(NODE_TYPE_ROOT), "root"}, {static_cast(NODE_TYPE_FOR), "for"}, {static_cast(NODE_TYPE_LET), "let"}, {static_cast(NODE_TYPE_FILTER), "filter"}, {static_cast(NODE_TYPE_RETURN), "return"}, {static_cast(NODE_TYPE_REMOVE), "remove"}, {static_cast(NODE_TYPE_INSERT), "insert"}, {static_cast(NODE_TYPE_UPDATE), "update"}, {static_cast(NODE_TYPE_REPLACE), "replace"}, {static_cast(NODE_TYPE_UPSERT), "upsert"}, {static_cast(NODE_TYPE_COLLECT), "collect"}, {static_cast(NODE_TYPE_SORT), "sort"}, {static_cast(NODE_TYPE_SORT_ELEMENT), "sort element"}, {static_cast(NODE_TYPE_LIMIT), "limit"}, {static_cast(NODE_TYPE_VARIABLE), "variable"}, {static_cast(NODE_TYPE_ASSIGN), "assign"}, {static_cast(NODE_TYPE_OPERATOR_UNARY_PLUS), "unary plus"}, {static_cast(NODE_TYPE_OPERATOR_UNARY_MINUS), "unary minus"}, {static_cast(NODE_TYPE_OPERATOR_UNARY_NOT), "unary not"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_AND), "logical and"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_OR), "logical or"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_PLUS), "plus"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_MINUS), "minus"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_TIMES), "times"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_DIV), "division"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_MOD), "modulus"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_EQ), "compare =="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_NE), "compare !="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_LT), "compare <"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_LE), "compare <="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_GT), "compare >"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_GE), "compare >="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_IN), "compare in"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_NIN), "compare not in"}, {static_cast(NODE_TYPE_OPERATOR_TERNARY), "ternary"}, {static_cast(NODE_TYPE_SUBQUERY), "subquery"}, {static_cast(NODE_TYPE_ATTRIBUTE_ACCESS), "attribute access"}, {static_cast(NODE_TYPE_BOUND_ATTRIBUTE_ACCESS), "bound attribute access"}, {static_cast(NODE_TYPE_INDEXED_ACCESS), "indexed access"}, {static_cast(NODE_TYPE_EXPANSION), "expansion"}, {static_cast(NODE_TYPE_ITERATOR), "iterator"}, {static_cast(NODE_TYPE_VALUE), "value"}, {static_cast(NODE_TYPE_ARRAY), "array"}, {static_cast(NODE_TYPE_OBJECT), "object"}, {static_cast(NODE_TYPE_OBJECT_ELEMENT), "object element"}, {static_cast(NODE_TYPE_COLLECTION), "collection"}, {static_cast(NODE_TYPE_REFERENCE), "reference"}, {static_cast(NODE_TYPE_PARAMETER), "parameter"}, {static_cast(NODE_TYPE_FCALL), "function call"}, {static_cast(NODE_TYPE_FCALL_USER), "user function call"}, {static_cast(NODE_TYPE_RANGE), "range"}, {static_cast(NODE_TYPE_NOP), "no-op"}, {static_cast(NODE_TYPE_COLLECT_COUNT), "collect with count"}, {static_cast(NODE_TYPE_CALCULATED_OBJECT_ELEMENT), "calculated object element"}, {static_cast(NODE_TYPE_EXAMPLE), "example"}, {static_cast(NODE_TYPE_PASSTHRU), "passthru"}, {static_cast(NODE_TYPE_ARRAY_LIMIT), "array limit"}, {static_cast(NODE_TYPE_DISTINCT), "distinct"}, {static_cast(NODE_TYPE_TRAVERSAL), "traversal"}, {static_cast(NODE_TYPE_DIRECTION), "direction"}, {static_cast(NODE_TYPE_COLLECTION_LIST), "collection list"}, {static_cast(NODE_TYPE_OPERATOR_NARY_AND), "n-ary and"}, {static_cast(NODE_TYPE_OPERATOR_NARY_OR), "n-ary or"}, {static_cast(NODE_TYPE_AGGREGATIONS), "aggregations array"}, {static_cast(NODE_TYPE_WITH), "with collections"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ), "array compare =="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_NE), "array compare !="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_LT), "array compare <"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_LE), "array compare <="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_GT), "array compare >"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_GE), "array compare >="}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_IN), "array compare in"}, {static_cast(NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN), "array compare not in"}, {static_cast(NODE_TYPE_QUANTIFIER), "quantifier"}, {static_cast(NODE_TYPE_SHORTEST_PATH), "shortest path"}}; /// @brief names for AST node value types std::unordered_map const AstNode::ValueTypeNames{ {static_cast(VALUE_TYPE_NULL), "null"}, {static_cast(VALUE_TYPE_BOOL), "bool"}, {static_cast(VALUE_TYPE_INT), "int"}, {static_cast(VALUE_TYPE_DOUBLE), "double"}, {static_cast(VALUE_TYPE_STRING), "string"}}; /// @brief resolve an attribute access static AstNode const* ResolveAttribute(AstNode const* node) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->type == NODE_TYPE_ATTRIBUTE_ACCESS); std::vector attributeNames; while (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) { attributeNames.emplace_back(node->getString()); node = node->getMember(0); } size_t which = attributeNames.size(); TRI_ASSERT(which > 0); while (which > 0) { TRI_ASSERT(node->type == NODE_TYPE_VALUE || node->type == NODE_TYPE_ARRAY || node->type == NODE_TYPE_OBJECT); bool found = false; if (node->type == NODE_TYPE_OBJECT) { TRI_ASSERT(which > 0); std::string const& attributeName = attributeNames[which - 1]; --which; size_t const n = node->numMembers(); for (size_t i = 0; i < n; ++i) { auto member = node->getMember(i); if (member->type == NODE_TYPE_OBJECT_ELEMENT && member->getString() == attributeName) { // found the attribute node = member->getMember(0); if (which == 0) { // we found what we looked for return node; } // we found the correct attribute but there is now an attribute // access on the result found = true; break; } } } if (!found) { break; } } // attribute not found or non-array return Ast::createNodeValueNull(); } /// @brief get the node type for inter-node comparisons static TRI_json_type_e GetNodeCompareType(AstNode const* node) { TRI_ASSERT(node != nullptr); if (node->type == NODE_TYPE_VALUE) { return JsonTypes[node->value.type]; } if (node->type == NODE_TYPE_ARRAY) { return TRI_JSON_ARRAY; } if (node->type == NODE_TYPE_OBJECT) { return TRI_JSON_OBJECT; } // we should never get here TRI_ASSERT(false); // return null in case assertions are turned off return TRI_JSON_NULL; } /// @brief compare two nodes /// @return range from -1 to +1 depending: /// - -1 LHS being less then RHS, /// - 0 LHS being equal RHS /// - 1 LHS being greater then RHS int arangodb::aql::CompareAstNodes(AstNode const* lhs, AstNode const* rhs, bool compareUtf8) { TRI_ASSERT(lhs != nullptr); TRI_ASSERT(rhs != nullptr); if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { lhs = ResolveAttribute(lhs); } if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { rhs = ResolveAttribute(rhs); } auto lType = GetNodeCompareType(lhs); auto rType = GetNodeCompareType(rhs); if (lType != rType) { int diff = static_cast(lType) - static_cast(rType); TRI_ASSERT(diff != 0); if (diff < 0) { return -1; } else if (diff > 0) { return 1; } TRI_ASSERT(false); return 0; } if (lType == TRI_JSON_NULL) { return 0; } if (lType == TRI_JSON_BOOLEAN) { int diff = static_cast(lhs->getIntValue() - rhs->getIntValue()); if (diff != 0) { if (diff < 0) { return -1; } return 1; } return 0; } if (lType == TRI_JSON_NUMBER) { double d = lhs->getDoubleValue() - rhs->getDoubleValue(); if (d < 0.0) { return -1; } else if (d > 0.0) { return 1; } return 0; } if (lType == TRI_JSON_STRING) { if (compareUtf8) { return TRI_compare_utf8(lhs->getStringValue(), lhs->getStringLength(), rhs->getStringValue(), rhs->getStringLength()); } size_t const minLength = (std::min)(lhs->getStringLength(), rhs->getStringLength()); int res = memcmp(lhs->getStringValue(), rhs->getStringValue(), minLength); if (res != 0) { return res; } int diff = static_cast(lhs->getStringLength()) - static_cast(rhs->getStringLength()); if (diff != 0) { return diff < 0 ? -1 : 1; } return 0; } if (lType == TRI_JSON_ARRAY) { size_t const numLhs = lhs->numMembers(); size_t const numRhs = rhs->numMembers(); size_t const n = ((numLhs > numRhs) ? numRhs : numLhs); for (size_t i = 0; i < n; ++i) { int res = arangodb::aql::CompareAstNodes(lhs->getMember(i), rhs->getMember(i), compareUtf8); if (res != 0) { return res; } } if (numLhs < numRhs) { return -1; } else if (numLhs > numRhs) { return 1; } return 0; } if (lType == TRI_JSON_OBJECT) { // this is a rather exceptional case, so we can // afford the inefficiency to convert the node to // JSON for comparison // (this saves us from writing our own compare function // for array AstNodes) std::unique_ptr lJson(lhs->toJsonValue(TRI_UNKNOWN_MEM_ZONE)); std::unique_ptr rJson(rhs->toJsonValue(TRI_UNKNOWN_MEM_ZONE)); return TRI_CompareValuesJson(lJson.get(), rJson.get(), compareUtf8); } // all things equal return 0; } /// @brief returns whether or not the string is empty static bool IsEmptyString(char const* p, size_t length) { char const* e = p + length; while (p < e) { if (*p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && *p != '\f' && *p != '\b') { return false; } ++p; } return true; } /// @brief create the node AstNode::AstNode(AstNodeType type) : type(type), flags(0), computedValue(nullptr) {} /// @brief create a node, with defining a value type AstNode::AstNode(AstNodeType type, AstNodeValueType valueType) : AstNode(type) { value.type = valueType; TRI_ASSERT(flags == 0); TRI_ASSERT(computedValue == nullptr); } /// @brief create a boolean node, with defining a value AstNode::AstNode(bool v, AstNodeValueType valueType) : AstNode(NODE_TYPE_VALUE, valueType) { TRI_ASSERT(valueType == VALUE_TYPE_BOOL); value.value._bool = v; TRI_ASSERT(flags == 0); TRI_ASSERT(computedValue == nullptr); } /// @brief create an int node, with defining a value AstNode::AstNode(int64_t v, AstNodeValueType valueType) : AstNode(NODE_TYPE_VALUE, valueType) { TRI_ASSERT(valueType == VALUE_TYPE_INT); value.value._int = v; TRI_ASSERT(flags == 0); TRI_ASSERT(computedValue == nullptr); } /// @brief create a string node, with defining a value AstNode::AstNode(char const* v, size_t length, AstNodeValueType valueType) : AstNode(NODE_TYPE_VALUE, valueType) { TRI_ASSERT(valueType == VALUE_TYPE_STRING); setStringValue(v, length); TRI_ASSERT(flags == 0); TRI_ASSERT(computedValue == nullptr); } /// @brief create the node from JSON AstNode::AstNode(Ast* ast, arangodb::basics::Json const& json) : AstNode(getNodeTypeFromJson(json)) { TRI_ASSERT(flags == 0); TRI_ASSERT(computedValue == nullptr); auto query = ast->query(); switch (type) { case NODE_TYPE_COLLECTION: case NODE_TYPE_PARAMETER: case NODE_TYPE_ATTRIBUTE_ACCESS: case NODE_TYPE_FCALL_USER: { std::string const str( JsonHelper::getStringValue(json.json(), "name", "")); value.type = VALUE_TYPE_STRING; setStringValue(query->registerString(str), str.size()); break; } case NODE_TYPE_VALUE: { int vType = JsonHelper::checkAndGetNumericValue(json.json(), "vTypeID"); validateValueType(vType); value.type = static_cast(vType); switch (value.type) { case VALUE_TYPE_NULL: break; case VALUE_TYPE_BOOL: value.value._bool = JsonHelper::checkAndGetBooleanValue(json.json(), "value"); break; case VALUE_TYPE_INT: setIntValue(JsonHelper::checkAndGetNumericValue(json.json(), "value")); break; case VALUE_TYPE_DOUBLE: setDoubleValue(JsonHelper::checkAndGetNumericValue( json.json(), "value")); break; case VALUE_TYPE_STRING: { std::string const str( JsonHelper::checkAndGetStringValue(json.json(), "value")); setStringValue(query->registerString(str), str.size()); break; } default: {} } break; } case NODE_TYPE_VARIABLE: { // TODO: fix this auto builder = JsonHelper::toVelocyPack(json.json()); auto variable = ast->variables()->createVariable(builder->slice()); TRI_ASSERT(variable != nullptr); setData(variable); break; } case NODE_TYPE_REFERENCE: { auto variableId = JsonHelper::checkAndGetNumericValue(json.json(), "id"); auto variable = ast->variables()->getVariable(variableId); TRI_ASSERT(variable != nullptr); setData(variable); break; } case NODE_TYPE_FCALL: { setData(query->executor()->getFunctionByName( JsonHelper::getStringValue(json.json(), "name", ""))); break; } case NODE_TYPE_OBJECT_ELEMENT: { std::string const str( JsonHelper::getStringValue(json.json(), "name", "")); setStringValue(query->registerString(str), str.size()); break; } case NODE_TYPE_EXPANSION: { setIntValue( JsonHelper::checkAndGetNumericValue(json.json(), "levels")); break; } case NODE_TYPE_OPERATOR_BINARY_IN: case NODE_TYPE_OPERATOR_BINARY_NIN: case NODE_TYPE_OPERATOR_BINARY_ARRAY_IN: case NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN: { setBoolValue(JsonHelper::getBooleanValue(json.json(), "sorted", false)); break; } case NODE_TYPE_ARRAY: { bool sorted = JsonHelper::getBooleanValue(json.json(), "sorted", false); if (sorted) { setFlag(DETERMINED_SORTED, VALUE_SORTED); } break; } case NODE_TYPE_QUANTIFIER: { std::string const quantifier(JsonHelper::getStringValue(json.json(), "quantifier", "")); setIntValue(Quantifier::FromString(quantifier)); break; } case NODE_TYPE_OBJECT: 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_UPSERT: case NODE_TYPE_COLLECT: case NODE_TYPE_COLLECT_COUNT: case NODE_TYPE_AGGREGATIONS: 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_ARRAY_EQ: case NODE_TYPE_OPERATOR_BINARY_ARRAY_NE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_LT: case NODE_TYPE_OPERATOR_BINARY_ARRAY_LE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_GT: case NODE_TYPE_OPERATOR_BINARY_ARRAY_GE: case NODE_TYPE_OPERATOR_TERNARY: case NODE_TYPE_SUBQUERY: case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS: case NODE_TYPE_INDEXED_ACCESS: case NODE_TYPE_ITERATOR: case NODE_TYPE_RANGE: case NODE_TYPE_NOP: case NODE_TYPE_CALCULATED_OBJECT_ELEMENT: case NODE_TYPE_EXAMPLE: case NODE_TYPE_PASSTHRU: case NODE_TYPE_ARRAY_LIMIT: case NODE_TYPE_DISTINCT: case NODE_TYPE_TRAVERSAL: case NODE_TYPE_SHORTEST_PATH: case NODE_TYPE_DIRECTION: case NODE_TYPE_COLLECTION_LIST: case NODE_TYPE_OPERATOR_NARY_AND: case NODE_TYPE_OPERATOR_NARY_OR: case NODE_TYPE_WITH: break; } Json subNodes = json.get("subNodes"); if (subNodes.isArray()) { size_t const len = subNodes.size(); for (size_t i = 0; i < len; i++) { Json subNode(subNodes.at(i)); int type = JsonHelper::checkAndGetNumericValue(subNode.json(), "typeID"); if (static_cast(type) == NODE_TYPE_NOP) { // special handling for nop as it is a singleton addMember(Ast::getNodeNop()); } else { addMember(new AstNode(ast, subNode)); } } } ast->query()->addNode(this); } /// @brief create the node from JSON AstNode::AstNode(std::function registerNode, std::function registerString, arangodb::basics::Json const& json) : AstNode(getNodeTypeFromJson(json)) { TRI_ASSERT(flags == 0); TRI_ASSERT(computedValue == nullptr); switch (type) { case NODE_TYPE_ATTRIBUTE_ACCESS: { std::string const str( JsonHelper::getStringValue(json.json(), "name", "")); value.type = VALUE_TYPE_STRING; auto p = registerString(str); TRI_ASSERT(p != nullptr); setStringValue(p, str.size()); break; } case NODE_TYPE_VALUE: { int vType = JsonHelper::checkAndGetNumericValue(json.json(), "vTypeID"); validateValueType(vType); value.type = static_cast(vType); switch (value.type) { case VALUE_TYPE_NULL: break; case VALUE_TYPE_BOOL: value.value._bool = JsonHelper::checkAndGetBooleanValue(json.json(), "value"); break; case VALUE_TYPE_INT: setIntValue(JsonHelper::checkAndGetNumericValue(json.json(), "value")); break; case VALUE_TYPE_DOUBLE: setDoubleValue(JsonHelper::checkAndGetNumericValue( json.json(), "value")); break; case VALUE_TYPE_STRING: { std::string const str( JsonHelper::checkAndGetStringValue(json.json(), "value")); setStringValue(registerString(str), str.size()); break; } default: {} } break; } case NODE_TYPE_REFERENCE: { // We use the edge here /* auto variableId = JsonHelper::checkAndGetNumericValue(json.json(), "id"); auto variable = ast->variables()->getVariable(variableId); TRI_ASSERT(variable != nullptr); setData(variable); */ break; } case NODE_TYPE_EXPANSION: { setIntValue( JsonHelper::checkAndGetNumericValue(json.json(), "levels")); break; } case NODE_TYPE_FCALL_USER: case NODE_TYPE_OBJECT_ELEMENT: case NODE_TYPE_COLLECTION: case NODE_TYPE_PARAMETER: case NODE_TYPE_VARIABLE: case NODE_TYPE_FCALL: 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_UPSERT: case NODE_TYPE_COLLECT: case NODE_TYPE_COLLECT_COUNT: case NODE_TYPE_AGGREGATIONS: case NODE_TYPE_SORT: case NODE_TYPE_SORT_ELEMENT: case NODE_TYPE_LIMIT: case NODE_TYPE_ASSIGN: case NODE_TYPE_SUBQUERY: case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS: case NODE_TYPE_CALCULATED_OBJECT_ELEMENT: case NODE_TYPE_EXAMPLE: case NODE_TYPE_DISTINCT: case NODE_TYPE_TRAVERSAL: case NODE_TYPE_SHORTEST_PATH: case NODE_TYPE_DIRECTION: case NODE_TYPE_COLLECTION_LIST: case NODE_TYPE_PASSTHRU: case NODE_TYPE_WITH: { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "Unsupported node type"); } case NODE_TYPE_OBJECT: case NODE_TYPE_ROOT: 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_BINARY_NIN: case NODE_TYPE_OPERATOR_TERNARY: case NODE_TYPE_INDEXED_ACCESS: case NODE_TYPE_ITERATOR: case NODE_TYPE_ARRAY: case NODE_TYPE_RANGE: case NODE_TYPE_NOP: case NODE_TYPE_ARRAY_LIMIT: case NODE_TYPE_OPERATOR_NARY_AND: case NODE_TYPE_OPERATOR_NARY_OR: case NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ: case NODE_TYPE_OPERATOR_BINARY_ARRAY_NE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_LT: case NODE_TYPE_OPERATOR_BINARY_ARRAY_LE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_GT: case NODE_TYPE_OPERATOR_BINARY_ARRAY_GE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_IN: case NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN: case NODE_TYPE_QUANTIFIER: break; } Json subNodes = json.get("subNodes"); if (subNodes.isArray()) { size_t const len = subNodes.size(); for (size_t i = 0; i < len; i++) { Json subNode(subNodes.at(i)); addMember(new AstNode(registerNode, registerString, subNode)); } } registerNode(this); } /// @brief destroy the node AstNode::~AstNode() { if (computedValue != nullptr) { delete[] computedValue; } } /// @brief return the string value of a node, as an std::string std::string AstNode::getString() const { TRI_ASSERT(type == NODE_TYPE_VALUE || type == NODE_TYPE_OBJECT_ELEMENT || type == NODE_TYPE_ATTRIBUTE_ACCESS || type == NODE_TYPE_PARAMETER || type == NODE_TYPE_COLLECTION || type == NODE_TYPE_BOUND_ATTRIBUTE_ACCESS || type == NODE_TYPE_FCALL_USER); TRI_ASSERT(value.type == VALUE_TYPE_STRING); return std::string(getStringValue(), getStringLength()); } /// @brief test if all members of a node are equality comparisons bool AstNode::isOnlyEqualityMatch() const { if (type != NODE_TYPE_OPERATOR_BINARY_AND && type != NODE_TYPE_OPERATOR_NARY_AND) { return false; } for (size_t i = 0; i < numMembers(); ++i) { auto op = getMember(i); if (op->type != arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ) { return false; } } return true; } /// @brief computes a hash value for a value node uint64_t AstNode::hashValue(uint64_t hash) const { if (type == NODE_TYPE_VALUE) { switch (value.type) { case VALUE_TYPE_NULL: return fasthash64(static_cast("null"), 4, hash); case VALUE_TYPE_BOOL: if (value.value._bool) { return fasthash64(static_cast("true"), 4, hash); } return fasthash64(static_cast("false"), 5, hash); case VALUE_TYPE_INT: return fasthash64(static_cast(&value.value._int), sizeof(value.value._int), hash); case VALUE_TYPE_DOUBLE: return fasthash64(static_cast(&value.value._double), sizeof(value.value._double), hash); case VALUE_TYPE_STRING: return fasthash64(static_cast(getStringValue()), getStringLength(), hash); } } size_t const n = numMembers(); if (type == NODE_TYPE_ARRAY) { hash = fasthash64(static_cast("array"), 5, hash); for (size_t i = 0; i < n; ++i) { hash = getMemberUnchecked(i)->hashValue(hash); } return hash; } if (type == NODE_TYPE_OBJECT) { hash = fasthash64(static_cast("object"), 6, hash); for (size_t i = 0; i < n; ++i) { auto sub = getMemberUnchecked(i); if (sub != nullptr) { hash = fasthash64(static_cast(sub->getStringValue()), sub->getStringLength(), hash); hash = sub->getMember(0)->hashValue(hash); } } return hash; } TRI_ASSERT(false); return 0; } /// @brief dump the node (for debugging purposes) #ifdef ARANGODB_ENABLE_MAINTAINER_MODE void AstNode::dump(int level) const { for (int i = 0; i < level; ++i) { std::cout << " "; } std::cout << "- " << getTypeString(); if (type == NODE_TYPE_VALUE || type == NODE_TYPE_ARRAY) { std::unique_ptr json(toJsonValue(TRI_UNKNOWN_MEM_ZONE)); std::cout << ": " << json.get(); } else if (type == NODE_TYPE_ATTRIBUTE_ACCESS) { std::cout << ": " << getString(); } else if (type == NODE_TYPE_REFERENCE) { std::cout << ": " << static_cast(getData())->name; } std::cout << "\n"; size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto sub = getMemberUnchecked(i); sub->dump(level + 1); } } #endif /// @brief compute the value for a constant value node /// the value is owned by the node and must not be freed by the caller /// note that the return value might be NULL in case of OOM VPackSlice AstNode::computeValue() const { TRI_ASSERT(isConstant()); if (computedValue == nullptr) { VPackBuilder builder; toVelocyPackValue(builder); computedValue = new uint8_t[builder.size()]; memcpy(computedValue, builder.data(), builder.size()); } return VPackSlice(computedValue); } /// @brief sort the members of an (array) node /// this will also set the VALUE_SORTED flag for the node void AstNode::sort() { TRI_ASSERT(type == NODE_TYPE_ARRAY); TRI_ASSERT(isConstant()); std::sort(members.begin(), members.end(), [](AstNode const* lhs, AstNode const* rhs) { return (arangodb::aql::CompareAstNodes(lhs, rhs, false) < 0); }); setFlag(DETERMINED_SORTED, VALUE_SORTED); } /// @brief return the type name of a node std::string const& AstNode::getTypeString() const { auto it = TypeNames.find(static_cast(type)); if (it != TypeNames.end()) { return (*it).second; } THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "missing node type in TypeNames"); } /// @brief return the value type name of a node std::string const& AstNode::getValueTypeString() const { auto it = ValueTypeNames.find(static_cast(value.type)); if (it != ValueTypeNames.end()) { return (*it).second; } THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "missing node type in ValueTypeNames"); } /// @brief stringify the AstNode std::string AstNode::toString(AstNode const* node) { std::unique_ptr json(node->toJson(TRI_UNKNOWN_MEM_ZONE, false)); return arangodb::basics::JsonHelper::toString(json.get()); } /// @brief checks whether we know a type of this kind; throws exception if not. void AstNode::validateType(int type) { auto it = TypeNames.find(static_cast(type)); if (it == TypeNames.end()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "unknown AST node TypeID"); } } /// @brief checks whether we know a value type of this kind; /// throws exception if not. void AstNode::validateValueType(int type) { auto it = ValueTypeNames.find(static_cast(type)); if (it == ValueTypeNames.end()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "invalid AST node valueTypeName"); } } /// @brief fetch a node's type from json AstNodeType AstNode::getNodeTypeFromJson(arangodb::basics::Json const& json) { int type = JsonHelper::checkAndGetNumericValue(json.json(), "typeID"); validateType(type); return static_cast(type); } /// @brief return a JSON representation of the node value /// the caller is responsible for freeing the JSON later TRI_json_t* AstNode::toJsonValue(TRI_memory_zone_t* zone) const { if (type == NODE_TYPE_VALUE) { // dump value of "value" node switch (value.type) { case VALUE_TYPE_NULL: return TRI_CreateNullJson(zone); case VALUE_TYPE_BOOL: return TRI_CreateBooleanJson(zone, value.value._bool); case VALUE_TYPE_INT: return TRI_CreateNumberJson(zone, static_cast(value.value._int)); case VALUE_TYPE_DOUBLE: return TRI_CreateNumberJson(zone, value.value._double); case VALUE_TYPE_STRING: return TRI_CreateStringCopyJson(zone, value.value._string, value.length); } } if (type == NODE_TYPE_ARRAY) { size_t const n = numMembers(); TRI_json_t* array = TRI_CreateArrayJson(zone, n); if (array == nullptr) { return nullptr; } for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); if (member != nullptr) { TRI_json_t* j = member->toJsonValue(zone); if (j != nullptr) { TRI_PushBack3ArrayJson(zone, array, j); } } } return array; } if (type == NODE_TYPE_OBJECT) { size_t const n = numMembers(); TRI_json_t* object = TRI_CreateObjectJson(zone, n); for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); if (member != nullptr) { TRI_json_t* j = member->getMember(0)->toJsonValue(zone); if (j != nullptr) { TRI_Insert3ObjectJson(zone, object, member->getStringValue(), j); } } } return object; } if (type == NODE_TYPE_ATTRIBUTE_ACCESS) { TRI_json_t* j = getMember(0)->toJsonValue(zone); if (j != nullptr) { if (TRI_IsObjectJson(j)) { TRI_json_t const* v = TRI_LookupObjectJson(j, getStringValue()); if (v != nullptr) { TRI_json_t* copy = TRI_CopyJson(zone, v); TRI_FreeJson(zone, j); return copy; } } TRI_FreeJson(zone, j); return TRI_CreateNullJson(zone); } } return nullptr; } /// @brief return a JSON representation of the node /// the caller is responsible for freeing the JSON later TRI_json_t* AstNode::toJson(TRI_memory_zone_t* zone, bool verbose) const { TRI_json_t* node = TRI_CreateObjectJson(zone); if (node == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } // dump node type std::string const& typeString(getTypeString()); TRI_json_t json; if (TRI_InitStringCopyJson(zone, &json, typeString.c_str(), typeString.size()) != TRI_ERROR_NO_ERROR) { TRI_FreeJson(zone, node); THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } TRI_Insert2ObjectJson(zone, node, "type", &json); // add typeId if (verbose) { TRI_Insert3ObjectJson(zone, node, "typeID", TRI_CreateNumberJson(zone, static_cast(type))); } if (type == NODE_TYPE_COLLECTION || type == NODE_TYPE_PARAMETER || type == NODE_TYPE_ATTRIBUTE_ACCESS || type == NODE_TYPE_OBJECT_ELEMENT || type == NODE_TYPE_FCALL_USER) { // dump "name" of node if (TRI_InitStringCopyJson(zone, &json, getStringValue(), getStringLength()) != TRI_ERROR_NO_ERROR) { TRI_FreeJson(zone, node); THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } TRI_Insert2ObjectJson(zone, node, "name", &json); } if (type == NODE_TYPE_FCALL) { auto func = static_cast(getData()); TRI_Insert3ObjectJson( zone, node, "name", TRI_CreateStringCopyJson(zone, func->externalName.c_str(), func->externalName.size())); // arguments are exported via node members } if (type == NODE_TYPE_ARRAY) { if (hasFlag(DETERMINED_SORTED)) { // transport information about a node's sortedness TRI_Insert3ObjectJson( zone, node, "sorted", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, hasFlag(VALUE_SORTED))); } } if (type == NODE_TYPE_VALUE) { // dump value of "value" node auto v = toJsonValue(zone); if (v == nullptr) { TRI_FreeJson(zone, node); THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } TRI_Insert3ObjectJson(zone, node, "value", v); if (verbose) { std::string const typeStr(getValueTypeString()); TRI_Insert3ObjectJson( zone, node, "vType", TRI_CreateStringCopyJson(zone, typeStr.c_str(), typeStr.size())); TRI_Insert3ObjectJson( zone, node, "vTypeID", TRI_CreateNumberJson(zone, static_cast(value.type))); } } if (type == NODE_TYPE_OPERATOR_BINARY_IN || type == NODE_TYPE_OPERATOR_BINARY_NIN || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_IN || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN) { TRI_Insert3ObjectJson( zone, node, "sorted", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, getBoolValue())); } if (type == NODE_TYPE_QUANTIFIER) { std::string const quantifier(Quantifier::Stringify(getIntValue(true))); TRI_Insert3ObjectJson( zone, node, "quantifier", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, quantifier.c_str(), quantifier.size())); } if (type == NODE_TYPE_VARIABLE || type == NODE_TYPE_REFERENCE) { auto variable = static_cast(getData()); TRI_ASSERT(variable != nullptr); TRI_Insert3ObjectJson(zone, node, "name", TRI_CreateStringCopyJson(zone, variable->name.c_str(), variable->name.size())); TRI_Insert3ObjectJson( zone, node, "id", TRI_CreateNumberJson(zone, static_cast(variable->id))); } if (type == NODE_TYPE_EXPANSION) { TRI_Insert3ObjectJson( zone, node, "levels", TRI_CreateNumberJson(zone, static_cast(getIntValue(true)))); } // dump sub-nodes size_t const n = members.size(); if (n > 0) { TRI_json_t* subNodes = TRI_CreateArrayJson(zone, n); if (subNodes == nullptr) { TRI_FreeJson(zone, node); THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } try { for (size_t i = 0; i < n; ++i) { AstNode* member = getMemberUnchecked(i); if (member != nullptr) { member->toJson(subNodes, zone, verbose); } } } catch (basics::Exception const&) { TRI_FreeJson(zone, subNodes); TRI_FreeJson(zone, node); throw; } catch (...) { TRI_FreeJson(zone, subNodes); TRI_FreeJson(zone, node); THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } TRI_Insert3ObjectJson(zone, node, "subNodes", subNodes); } return node; } /// @brief return a VelocyPack representation of the node value std::shared_ptr AstNode::toVelocyPackValue() const { auto builder = std::make_shared(); if (builder == nullptr) { return nullptr; } toVelocyPackValue(*builder); return builder; } /// @brief build a VelocyPack representation of the node value /// Can throw Out of Memory Error void AstNode::toVelocyPackValue(VPackBuilder& builder) const { if (type == NODE_TYPE_VALUE) { // dump value of "value" node switch (value.type) { case VALUE_TYPE_NULL: builder.add(VPackValue(VPackValueType::Null)); break; case VALUE_TYPE_BOOL: builder.add(VPackValue(value.value._bool)); break; case VALUE_TYPE_INT: builder.add(VPackValue(static_cast(value.value._int))); break; case VALUE_TYPE_DOUBLE: builder.add(VPackValue(value.value._double)); break; case VALUE_TYPE_STRING: builder.add(VPackValue(std::string(value.value._string, value.length))); break; } return; } if (type == NODE_TYPE_ARRAY) { { VPackArrayBuilder guard(&builder); size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); if (member != nullptr) { member->toVelocyPackValue(builder); } } } return; } if (type == NODE_TYPE_OBJECT) { { size_t const n = numMembers(); VPackObjectBuilder guard(&builder); for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); if (member != nullptr) { builder.add(VPackValue(member->getString())); member->getMember(0)->toVelocyPackValue(builder); } } } return; } if (type == NODE_TYPE_ATTRIBUTE_ACCESS) { // TODO Could this be done more efficiently in the builder in place? auto tmp = getMember(0)->toVelocyPackValue(); if (tmp != nullptr) { VPackSlice slice = tmp->slice(); if (slice.isObject()) { slice = slice.get(getString()); if (!slice.isNone()) { builder.add(slice); return; } } builder.add(VPackValue(VPackValueType::Null)); } } // Do not add anything. } /// @brief return a VelocyPack representation of the node std::shared_ptr AstNode::toVelocyPack(bool verbose) const { auto builder = std::make_shared(); if (builder == nullptr) { return nullptr; } toVelocyPack(*builder, verbose); return builder; } /// @brief Create a VelocyPack representation of the node void AstNode::toVelocyPack(VPackBuilder& builder, bool verbose) const { try { VPackObjectBuilder guard(&builder); // dump node type builder.add("type", VPackValue(getTypeString())); if (verbose) { builder.add("typeID", VPackValue(static_cast(type))); } if (type == NODE_TYPE_COLLECTION || type == NODE_TYPE_PARAMETER || type == NODE_TYPE_ATTRIBUTE_ACCESS || type == NODE_TYPE_OBJECT_ELEMENT || type == NODE_TYPE_FCALL_USER) { // dump "name" of node builder.add("name", VPackValue(getString())); } if (type == NODE_TYPE_FCALL) { auto func = static_cast(getData()); builder.add("name", VPackValue(func->externalName)); // arguments are exported via node members } if (type == NODE_TYPE_ARRAY && hasFlag(DETERMINED_SORTED)) { // transport information about a node's sortedness builder.add("sorted", VPackValue(hasFlag(VALUE_SORTED))); } if (type == NODE_TYPE_VALUE) { // dump value of "value" node builder.add(VPackValue("value")); toVelocyPackValue(builder); if (verbose) { builder.add("vType", VPackValue(getValueTypeString())); builder.add("vTypeID", VPackValue(static_cast(value.type))); } } if (type == NODE_TYPE_OPERATOR_BINARY_IN || type == NODE_TYPE_OPERATOR_BINARY_NIN || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_IN || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN) { builder.add("sorted", VPackValue(getBoolValue())); } if (type == NODE_TYPE_QUANTIFIER) { std::string const quantifier(Quantifier::Stringify(getIntValue(true))); builder.add("quantifier", VPackValue(quantifier)); } if (type == NODE_TYPE_VARIABLE || type == NODE_TYPE_REFERENCE) { auto variable = static_cast(getData()); TRI_ASSERT(variable != nullptr); builder.add("name", VPackValue(variable->name)); builder.add("id", VPackValue(static_cast(variable->id))); } if (type == NODE_TYPE_EXPANSION) { builder.add("levels", VPackValue(static_cast(getIntValue(true)))); } // dump sub-nodes size_t const n = members.size(); if (n > 0) { builder.add(VPackValue("subNodes")); VPackArrayBuilder guard(&builder); for (size_t i = 0; i < n; ++i) { AstNode* member = getMemberUnchecked(i); if (member != nullptr) { member->toVelocyPack(builder, verbose); } } } } catch (...) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } } /// @brief iterates whether a node of type "searchType" can be found bool AstNode::containsNodeType(AstNodeType searchType) const { if (type == searchType) { return true; } // iterate sub-nodes size_t const n = members.size(); if (n > 0) { for (size_t i = 0; i < n; ++i) { AstNode* member = getMemberUnchecked(i); if (member != nullptr && member->containsNodeType(searchType)) { return true; } } } return false; } /// @brief convert the node's value to a boolean value /// this may create a new node or return the node itself if it is already a /// boolean value node AstNode* AstNode::castToBool(Ast* ast) { TRI_ASSERT(type == NODE_TYPE_VALUE || type == NODE_TYPE_ARRAY || type == NODE_TYPE_OBJECT); if (type == NODE_TYPE_VALUE) { switch (value.type) { case VALUE_TYPE_NULL: // null => false return ast->createNodeValueBool(false); case VALUE_TYPE_BOOL: // already a boolean return this; case VALUE_TYPE_INT: return ast->createNodeValueBool(value.value._int != 0); case VALUE_TYPE_DOUBLE: return ast->createNodeValueBool(value.value._double != 0.0); case VALUE_TYPE_STRING: return ast->createNodeValueBool(value.length > 0); default: {} } // fall-through intentional } else if (type == NODE_TYPE_ARRAY) { return ast->createNodeValueBool(true); } else if (type == NODE_TYPE_OBJECT) { return ast->createNodeValueBool(true); } return ast->createNodeValueBool(false); } /// @brief convert the node's value to a number value /// this may create a new node or return the node itself if it is already a /// numeric value node AstNode* AstNode::castToNumber(Ast* ast) { TRI_ASSERT(type == NODE_TYPE_VALUE || type == NODE_TYPE_ARRAY || type == NODE_TYPE_OBJECT); if (type == NODE_TYPE_VALUE) { switch (value.type) { case VALUE_TYPE_NULL: // null => 0 return ast->createNodeValueInt(0); case VALUE_TYPE_BOOL: // false => 0, true => 1 return ast->createNodeValueInt(value.value._bool ? 1 : 0); case VALUE_TYPE_INT: case VALUE_TYPE_DOUBLE: // already numeric! return this; case VALUE_TYPE_STRING: try { // try converting string to number double v = std::stod(std::string(value.value._string, value.length)); return ast->createNodeValueDouble(v); } catch (...) { if (IsEmptyString(value.value._string, value.length)) { // empty string => 0 return ast->createNodeValueInt(0); } // conversion failed } // fall-through intentional } // fall-through intentional } else if (type == NODE_TYPE_ARRAY) { size_t const n = numMembers(); if (n == 0) { // [ ] => 0 return ast->createNodeValueInt(0); } else if (n == 1) { auto member = getMember(0); // convert only member to number return member->castToNumber(ast); } // fall-through intentional } else if (type == NODE_TYPE_OBJECT) { // fall-through intentional } return ast->createNodeValueInt(0); } /// @brief adds a JSON representation of the node to the JSON list specified /// in the first argument void AstNode::toJson(TRI_json_t* json, TRI_memory_zone_t* zone, bool verbose) const { TRI_ASSERT(TRI_IsArrayJson(json)); TRI_json_t* node = toJson(zone, verbose); if (node == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } TRI_PushBack3ArrayJson(zone, json, node); } /// @brief get the integer value of a node, provided the node is a value node /// this will return 0 for all non-value nodes and for all non-int value nodes!! int64_t AstNode::getIntValue() const { if (type == NODE_TYPE_VALUE) { switch (value.type) { case VALUE_TYPE_BOOL: return (value.value._bool ? 1 : 0); case VALUE_TYPE_INT: return value.value._int; case VALUE_TYPE_DOUBLE: return static_cast(value.value._double); default: {} } } return 0; } /// @brief get the double value of a node, provided the node is a value node double AstNode::getDoubleValue() const { if (type == NODE_TYPE_VALUE) { switch (value.type) { case VALUE_TYPE_BOOL: return (value.value._bool ? 1.0 : 0.0); case VALUE_TYPE_INT: return static_cast(value.value._int); case VALUE_TYPE_DOUBLE: return value.value._double; default: {} } } return 0.0; } /// @brief whether or not the node value is trueish bool AstNode::isTrue() const { if (type == NODE_TYPE_VALUE) { switch (value.type) { case VALUE_TYPE_NULL: return false; case VALUE_TYPE_BOOL: return value.value._bool; case VALUE_TYPE_INT: return (value.value._int != 0); case VALUE_TYPE_DOUBLE: return (value.value._double != 0.0); case VALUE_TYPE_STRING: return (value.length != 0); } } else if (type == NODE_TYPE_ARRAY || type == NODE_TYPE_OBJECT) { return true; } else if (type == NODE_TYPE_OPERATOR_BINARY_OR) { if (getMember(0)->isTrue()) { return true; } return getMember(1)->isTrue(); } else if (type == NODE_TYPE_OPERATOR_BINARY_AND) { if (!getMember(0)->isTrue()) { return false; } return getMember(1)->isTrue(); } else if (type == NODE_TYPE_OPERATOR_UNARY_NOT) { if (getMember(0)->isFalse()) { // ! false => true return true; } } return false; } /// @brief whether or not the node value is falsey bool AstNode::isFalse() const { if (type == NODE_TYPE_VALUE) { switch (value.type) { case VALUE_TYPE_NULL: return true; case VALUE_TYPE_BOOL: return (!value.value._bool); case VALUE_TYPE_INT: return (value.value._int == 0); case VALUE_TYPE_DOUBLE: return (value.value._double == 0.0); case VALUE_TYPE_STRING: return (value.length == 0); } } else if (type == NODE_TYPE_ARRAY || type == NODE_TYPE_OBJECT) { return false; } else if (type == NODE_TYPE_OPERATOR_BINARY_OR) { if (!getMember(0)->isFalse()) { return false; } return getMember(1)->isFalse(); } else if (type == NODE_TYPE_OPERATOR_BINARY_AND) { if (getMember(0)->isFalse()) { return true; } return getMember(1)->isFalse(); } else if (type == NODE_TYPE_OPERATOR_UNARY_NOT) { if (getMember(0)->isTrue()) { // ! true => false return true; } } return false; } /// @brief whether or not a value node is of type attribute access that /// refers to any variable reference /// returns true if yes, and then also returns variable reference and array /// of attribute names in the parameter passed by reference bool AstNode::isAttributeAccessForVariable( std::pair>& result) const { if (type != NODE_TYPE_ATTRIBUTE_ACCESS && type != NODE_TYPE_EXPANSION) { return false; } // initialize bool expandNext = false; result.first = nullptr; if (!result.second.empty()) { result.second.clear(); } auto node = this; while (node->type == NODE_TYPE_ATTRIBUTE_ACCESS || node->type == NODE_TYPE_EXPANSION) { if (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) { result.second.insert( result.second.begin(), arangodb::basics::AttributeName(node->getString(), expandNext)); node = node->getMember(0); expandNext = false; } else { // expansion, i.e. [*] TRI_ASSERT(node->type == NODE_TYPE_EXPANSION); TRI_ASSERT(node->numMembers() >= 2); // check if the expansion uses a projection. if yes, we cannot use an // index for it if (node->getMember(4) != Ast::getNodeNop()) { // [* RETURN projection] result.second.clear(); return false; } if (node->getMember(1)->type != NODE_TYPE_REFERENCE) { if (!node->getMember(1)->isAttributeAccessForVariable(result)) { result.second.clear(); return false; } } expandNext = true; TRI_ASSERT(node->getMember(0)->type == NODE_TYPE_ITERATOR); node = node->getMember(0)->getMember(1); } } if (node->type != NODE_TYPE_REFERENCE) { result.second.clear(); return false; } result.first = static_cast(node->getData()); return true; } /// @brief recursively clear flags void AstNode::clearFlagsRecursive() { clearFlags(); size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); member->clearFlagsRecursive(); } } /// @brief whether or not a node is simple enough to be used in a simple /// expression bool AstNode::isSimple() const { if (hasFlag(DETERMINED_SIMPLE)) { // fast track exit return hasFlag(VALUE_SIMPLE); } if (type == NODE_TYPE_REFERENCE || type == NODE_TYPE_VALUE || type == NODE_TYPE_VARIABLE || type == NODE_TYPE_NOP) { setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE); return true; } if (type == NODE_TYPE_ARRAY || type == NODE_TYPE_OBJECT || type == NODE_TYPE_EXPANSION || type == NODE_TYPE_ITERATOR || type == NODE_TYPE_ARRAY_LIMIT || type == NODE_TYPE_CALCULATED_OBJECT_ELEMENT) { size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); if (!member->isSimple()) { setFlag(DETERMINED_SIMPLE); return false; } } setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE); return true; } if (type == NODE_TYPE_OBJECT_ELEMENT || type == NODE_TYPE_ATTRIBUTE_ACCESS || type == NODE_TYPE_OPERATOR_UNARY_NOT) { TRI_ASSERT(numMembers() == 1); if (!getMember(0)->isSimple()) { setFlag(DETERMINED_SIMPLE); return false; } setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE); return true; } if (type == NODE_TYPE_BOUND_ATTRIBUTE_ACCESS) { if (!getMember(0)->isSimple()) { setFlag(DETERMINED_SIMPLE); return false; } setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE); return true; } if (type == NODE_TYPE_FCALL) { // some functions have C++ handlers // check if the called function is one of them auto func = static_cast(getData()); TRI_ASSERT(func != nullptr); if (func->implementation == nullptr) { // no C++ handler available for function setFlag(DETERMINED_SIMPLE); return false; } TRI_ASSERT(func->implementation != nullptr); TRI_ASSERT(numMembers() == 1); // check if there is a C++ function handler condition if (func->condition != nullptr && !func->condition()) { // function execution condition is false setFlag(DETERMINED_SIMPLE); return false; } // check simplicity of function arguments auto args = getMember(0); size_t const n = args->numMembers(); for (size_t i = 0; i < n; ++i) { auto member = args->getMemberUnchecked(i); auto conversion = func->getArgumentConversion(i); if (member->type == NODE_TYPE_COLLECTION && (conversion == Function::CONVERSION_REQUIRED || conversion == Function::CONVERSION_OPTIONAL)) { // collection attribute: no need to check for member simplicity continue; } else { // check for member simplicity the usual way if (!member->isSimple()) { setFlag(DETERMINED_SIMPLE); return false; } } } setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE); return true; } if (type == NODE_TYPE_OPERATOR_BINARY_PLUS || type == NODE_TYPE_OPERATOR_BINARY_MINUS || type == NODE_TYPE_OPERATOR_BINARY_TIMES || type == NODE_TYPE_OPERATOR_BINARY_DIV || type == NODE_TYPE_OPERATOR_BINARY_MOD) { if (!getMember(0)->isSimple() || !getMember(1)->isSimple()) { setFlag(DETERMINED_SIMPLE); return false; } setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE); return true; } if (type == NODE_TYPE_OPERATOR_BINARY_AND || type == NODE_TYPE_OPERATOR_BINARY_OR || type == NODE_TYPE_OPERATOR_BINARY_EQ || type == NODE_TYPE_OPERATOR_BINARY_NE || type == NODE_TYPE_OPERATOR_BINARY_LT || type == NODE_TYPE_OPERATOR_BINARY_LE || type == NODE_TYPE_OPERATOR_BINARY_GT || type == NODE_TYPE_OPERATOR_BINARY_GE || type == NODE_TYPE_OPERATOR_BINARY_IN || type == NODE_TYPE_OPERATOR_BINARY_NIN || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NE || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_LT || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_LE || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_GT || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_GE || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_IN || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN || type == NODE_TYPE_RANGE || type == NODE_TYPE_INDEXED_ACCESS || type == NODE_TYPE_PASSTHRU) { // a logical operator is simple if its operands are simple // a comparison operator is simple if both bounds are simple // a range is simple if both bounds are simple if (!getMember(0)->isSimple() || !getMember(1)->isSimple()) { setFlag(DETERMINED_SIMPLE); return false; } setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE); return true; } setFlag(DETERMINED_SIMPLE); return false; } /// @brief whether or not a node has a constant value bool AstNode::isConstant() const { if (hasFlag(DETERMINED_CONSTANT)) { // fast track exit return hasFlag(VALUE_CONSTANT); } if (type == NODE_TYPE_VALUE) { setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT); return true; } if (type == NODE_TYPE_ARRAY) { size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMember(i); if (!member->isConstant()) { setFlag(DETERMINED_CONSTANT); return false; } } setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT); return true; } if (type == NODE_TYPE_OBJECT) { size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMember(i); if (member->type == NODE_TYPE_OBJECT_ELEMENT) { auto value = member->getMember(0); if (!value->isConstant()) { setFlag(DETERMINED_CONSTANT); return false; } } else { // definitely not const! TRI_ASSERT(member->type == NODE_TYPE_CALCULATED_OBJECT_ELEMENT); setFlag(DETERMINED_CONSTANT); return false; } } setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT); return true; } if (type == NODE_TYPE_ATTRIBUTE_ACCESS || type == NODE_TYPE_BOUND_ATTRIBUTE_ACCESS) { if (getMember(0)->isConstant()) { setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT); return true; } } setFlag(DETERMINED_CONSTANT); return false; } /// @brief whether or not a node is a simple comparison operator bool AstNode::isSimpleComparisonOperator() const { return (type == NODE_TYPE_OPERATOR_BINARY_EQ || type == NODE_TYPE_OPERATOR_BINARY_NE || type == NODE_TYPE_OPERATOR_BINARY_LT || type == NODE_TYPE_OPERATOR_BINARY_LE || type == NODE_TYPE_OPERATOR_BINARY_GT || type == NODE_TYPE_OPERATOR_BINARY_GE); } /// @brief whether or not a node is a comparison operator bool AstNode::isComparisonOperator() const { return (type == NODE_TYPE_OPERATOR_BINARY_EQ || type == NODE_TYPE_OPERATOR_BINARY_NE || type == NODE_TYPE_OPERATOR_BINARY_LT || type == NODE_TYPE_OPERATOR_BINARY_LE || type == NODE_TYPE_OPERATOR_BINARY_GT || type == NODE_TYPE_OPERATOR_BINARY_GE || type == NODE_TYPE_OPERATOR_BINARY_IN || type == NODE_TYPE_OPERATOR_BINARY_NIN); } /// @brief whether or not a node is an array comparison operator bool AstNode::isArrayComparisonOperator() const { return (type == NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NE || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_LT || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_LE || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_GT || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_GE || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_IN || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN); } /// @brief whether or not a node (and its subnodes) can throw a runtime /// exception bool AstNode::canThrow() const { if (hasFlag(DETERMINED_THROWS)) { // fast track exit return hasFlag(VALUE_THROWS); } // check sub-nodes first size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMember(i); if (member->canThrow()) { // if any sub-node may throw, the whole branch may throw setFlag(DETERMINED_THROWS); return true; } } // no sub-node throws, now check ourselves if (type == NODE_TYPE_FCALL) { auto func = static_cast(getData()); // built-in functions may or may not throw // we are currently reporting non-deterministic functions as // potentially throwing. This is not correct on the one hand, but on // the other hand we must not optimize or move non-deterministic functions // during optimization if (func->canThrow) { setFlag(DETERMINED_THROWS, VALUE_THROWS); return true; } setFlag(DETERMINED_THROWS); return false; } if (type == NODE_TYPE_FCALL_USER) { // user functions can always throw setFlag(DETERMINED_THROWS, VALUE_THROWS); return true; } // everything else does not throw! setFlag(DETERMINED_THROWS); return false; } /// @brief whether or not a node (and its subnodes) can safely be executed on /// a DB server bool AstNode::canRunOnDBServer() const { if (hasFlag(DETERMINED_RUNONDBSERVER)) { // fast track exit return hasFlag(VALUE_RUNONDBSERVER); } // check sub-nodes first size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMember(i); if (!member->canRunOnDBServer()) { // if any sub-node cannot run on a DB server, we can't either setFlag(DETERMINED_RUNONDBSERVER); return false; } } // now check ourselves if (type == NODE_TYPE_FCALL) { // built-in function auto func = static_cast(getData()); if (func->canRunOnDBServer) { setFlag(DETERMINED_RUNONDBSERVER, VALUE_RUNONDBSERVER); } else { setFlag(DETERMINED_RUNONDBSERVER); } return func->canRunOnDBServer; } if (type == NODE_TYPE_FCALL_USER) { // user function. we don't know anything about it setFlag(DETERMINED_RUNONDBSERVER); return false; } // everyhing else can run everywhere setFlag(DETERMINED_RUNONDBSERVER, VALUE_RUNONDBSERVER); return true; } /// @brief whether or not an object's keys must be checked for uniqueness bool AstNode::mustCheckUniqueness() const { if (hasFlag(DETERMINED_CHECKUNIQUENESS)) { // fast track exit return hasFlag(VALUE_CHECKUNIQUENESS); } TRI_ASSERT(type == NODE_TYPE_OBJECT); bool mustCheck = false; // check the actual key members now size_t const n = numMembers(); if (n >= 2) { std::unordered_set keys; // only useful to check when there are 2 or more keys for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); if (member->type == NODE_TYPE_OBJECT_ELEMENT) { // constant key if (!keys.emplace(member->getString()).second) { // duplicate key mustCheck = true; break; } } else if (member->type == NODE_TYPE_CALCULATED_OBJECT_ELEMENT) { // dynamic key... we don't know the key yet, so there's no // way around check it at runtime later mustCheck = true; break; } } } if (mustCheck) { // we have duplicate keys, or we can't tell yet setFlag(DETERMINED_CHECKUNIQUENESS, VALUE_CHECKUNIQUENESS); } else { // all keys unique, or just one or zero keys in object setFlag(DETERMINED_CHECKUNIQUENESS); } return mustCheck; } /// @brief whether or not a node (and its subnodes) is deterministic bool AstNode::isDeterministic() const { if (hasFlag(DETERMINED_NONDETERMINISTIC)) { // fast track exit return !hasFlag(VALUE_NONDETERMINISTIC); } if (isConstant()) { return true; } // check sub-nodes first size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); if (!member->isDeterministic()) { // if any sub-node is non-deterministic, we are neither setFlag(DETERMINED_NONDETERMINISTIC, VALUE_NONDETERMINISTIC); return false; } } if (type == NODE_TYPE_FCALL) { // built-in functions may or may not be deterministic auto func = static_cast(getData()); if (!func->isDeterministic) { setFlag(DETERMINED_NONDETERMINISTIC, VALUE_NONDETERMINISTIC); return false; } setFlag(DETERMINED_NONDETERMINISTIC); return true; } if (type == NODE_TYPE_FCALL_USER) { // user functions are always non-deterministic setFlag(DETERMINED_NONDETERMINISTIC, VALUE_NONDETERMINISTIC); return false; } // everything else is deterministic setFlag(DETERMINED_NONDETERMINISTIC); return true; } /// @brief whether or not a node (and its subnodes) is cacheable bool AstNode::isCacheable() const { if (isConstant()) { return true; } // check sub-nodes first size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); if (!member->isCacheable()) { return false; } } if (type == NODE_TYPE_FCALL) { // built-in functions may or may not be cacheable auto func = static_cast(getData()); return func->isCacheable; } if (type == NODE_TYPE_FCALL_USER) { // user functions are always non-cacheable return false; } // everything else is cacheable return true; } /// @brief whether or not a node (and its subnodes) may contain a call to a /// user-defined function bool AstNode::callsUserDefinedFunction() const { if (isConstant()) { return true; } // check sub-nodes first size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { auto member = getMemberUnchecked(i); if (!member->callsUserDefinedFunction()) { return false; } } return (type == NODE_TYPE_FCALL_USER); } /// @brief whether or not the object node contains dynamically named attributes /// on its first level bool AstNode::containsDynamicAttributeName() const { TRI_ASSERT(type == NODE_TYPE_OBJECT); size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { if (getMember(i)->type == NODE_TYPE_CALCULATED_OBJECT_ELEMENT) { return true; } } return false; } /// @brief clone a node, recursively AstNode* AstNode::clone(Ast* ast) const { return ast->clone(this); } /// @brief append a string representation of the node to a string buffer /// the string representation does not need to be JavaScript-compatible /// except for node types NODE_TYPE_VALUE, NODE_TYPE_ARRAY and NODE_TYPE_OBJECT /// (only for objects that do not contain dynamic attributes) void AstNode::stringify(arangodb::basics::StringBuffer* buffer, bool verbose, bool failIfLong) const { // any arrays/objects with more values than this will not be stringified if // failIfLonf is set to true! static size_t const TooLongThreshold = 80; if (type == NODE_TYPE_VALUE) { // must be JavaScript-compatible! appendValue(buffer); return; } if (type == NODE_TYPE_ARRAY) { // must be JavaScript-compatible! size_t const n = numMembers(); if (failIfLong && n > TooLongThreshold) { // intentionally do not stringify this node because the output would be // too long THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); } buffer->appendChar('['); for (size_t i = 0; i < n; ++i) { if (i > 0) { buffer->appendChar(','); } AstNode* member = getMember(i); if (member != nullptr) { member->stringify(buffer, verbose, failIfLong); } } buffer->appendChar(']'); return; } if (type == NODE_TYPE_OBJECT) { // must be JavaScript-compatible! buffer->appendChar('{'); size_t const n = numMembers(); if (failIfLong && n > TooLongThreshold) { // intentionally do not stringify this node because the output would be // too long THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); } for (size_t i = 0; i < n; ++i) { if (i > 0) { buffer->appendChar(','); } AstNode* member = getMember(i); if (member->type == NODE_TYPE_OBJECT_ELEMENT) { TRI_ASSERT(member->numMembers() == 1); buffer->appendJsonEncoded(member->getStringValue(), member->getStringLength()); buffer->appendChar(':'); member->getMember(0)->stringify(buffer, verbose, failIfLong); } else if (member->type == NODE_TYPE_CALCULATED_OBJECT_ELEMENT) { TRI_ASSERT(member->numMembers() == 2); buffer->appendText(TRI_CHAR_LENGTH_PAIR("$[")); member->getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendText(TRI_CHAR_LENGTH_PAIR("]:")); member->getMember(1)->stringify(buffer, verbose, failIfLong); } else { TRI_ASSERT(false); } } buffer->appendChar('}'); return; } if (type == NODE_TYPE_REFERENCE || type == NODE_TYPE_VARIABLE) { // not used by V8 auto variable = static_cast(getData()); TRI_ASSERT(variable != nullptr); // we're intentionally not using the variable name as it is not necessarily // unique within a query (hey COLLECT, I am looking at you!) buffer->appendChar('$'); buffer->appendInteger(variable->id); return; } if (type == NODE_TYPE_INDEXED_ACCESS) { // not used by V8 auto member = getMember(0); auto index = getMember(1); member->stringify(buffer, verbose, failIfLong); buffer->appendChar('['); index->stringify(buffer, verbose, failIfLong); buffer->appendChar(']'); return; } if (type == NODE_TYPE_ATTRIBUTE_ACCESS) { // not used by V8 auto member = getMember(0); member->stringify(buffer, verbose, failIfLong); buffer->appendChar('.'); buffer->appendText(getStringValue(), getStringLength()); return; } if (type == NODE_TYPE_BOUND_ATTRIBUTE_ACCESS) { // not used by V8 getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendChar('.'); getMember(1)->stringify(buffer, verbose, failIfLong); return; } if (type == NODE_TYPE_PARAMETER) { // not used by V8 buffer->appendChar('@'); buffer->appendText(getStringValue(), getStringLength()); return; } if (type == NODE_TYPE_FCALL) { // not used by V8 auto func = static_cast(getData()); buffer->appendText(func->externalName); buffer->appendChar('('); getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendChar(')'); return; } if (type == NODE_TYPE_ARRAY_LIMIT) { // not used by V8 buffer->appendText(TRI_CHAR_LENGTH_PAIR("_LIMIT(")); getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendChar(','); getMember(1)->stringify(buffer, verbose, failIfLong); buffer->appendChar(')'); return; } if (type == NODE_TYPE_EXPANSION) { // not used by V8 buffer->appendText(TRI_CHAR_LENGTH_PAIR("_EXPANSION(")); getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendChar(','); getMember(1)->stringify(buffer, verbose, failIfLong); // filter buffer->appendChar(','); auto filterNode = getMember(2); if (filterNode != nullptr && filterNode != Ast::getNodeNop()) { buffer->appendText(TRI_CHAR_LENGTH_PAIR(" FILTER ")); filterNode->getMember(0)->stringify(buffer, verbose, failIfLong); } auto limitNode = getMember(3); if (limitNode != nullptr && limitNode != Ast::getNodeNop()) { buffer->appendText(TRI_CHAR_LENGTH_PAIR(" LIMIT ")); limitNode->getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendChar(','); limitNode->getMember(1)->stringify(buffer, verbose, failIfLong); } auto returnNode = getMember(4); if (returnNode != nullptr && returnNode != Ast::getNodeNop()) { buffer->appendText(TRI_CHAR_LENGTH_PAIR(" RETURN ")); returnNode->stringify(buffer, verbose, failIfLong); } buffer->appendChar(')'); return; } if (type == NODE_TYPE_ITERATOR) { // not used by V8 buffer->appendText(TRI_CHAR_LENGTH_PAIR("_ITERATOR(")); getMember(1)->stringify(buffer, verbose, failIfLong); buffer->appendChar(','); getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendChar(')'); return; } if (type == NODE_TYPE_OPERATOR_UNARY_NOT || type == NODE_TYPE_OPERATOR_UNARY_PLUS || type == NODE_TYPE_OPERATOR_UNARY_MINUS) { // not used by V8 TRI_ASSERT(numMembers() == 1); auto it = Operators.find(static_cast(type)); TRI_ASSERT(it != Operators.end()); buffer->appendChar(' '); buffer->appendText((*it).second); getMember(0)->stringify(buffer, verbose, failIfLong); return; } if (type == NODE_TYPE_OPERATOR_BINARY_AND || type == NODE_TYPE_OPERATOR_BINARY_OR || type == NODE_TYPE_OPERATOR_BINARY_PLUS || type == NODE_TYPE_OPERATOR_BINARY_MINUS || type == NODE_TYPE_OPERATOR_BINARY_TIMES || type == NODE_TYPE_OPERATOR_BINARY_DIV || type == NODE_TYPE_OPERATOR_BINARY_MOD || type == NODE_TYPE_OPERATOR_BINARY_EQ || type == NODE_TYPE_OPERATOR_BINARY_NE || type == NODE_TYPE_OPERATOR_BINARY_LT || type == NODE_TYPE_OPERATOR_BINARY_LE || type == NODE_TYPE_OPERATOR_BINARY_GT || type == NODE_TYPE_OPERATOR_BINARY_GE || type == NODE_TYPE_OPERATOR_BINARY_IN || type == NODE_TYPE_OPERATOR_BINARY_NIN) { // not used by V8 TRI_ASSERT(numMembers() == 2); auto it = Operators.find(type); TRI_ASSERT(it != Operators.end()); getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendChar(' '); buffer->appendText((*it).second); buffer->appendChar(' '); getMember(1)->stringify(buffer, verbose, failIfLong); return; } if (type == NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NE || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_LT || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_LE || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_GT || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_GE || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_IN || type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN) { // not used by V8 TRI_ASSERT(numMembers() == 3); auto it = Operators.find(type); TRI_ASSERT(it != Operators.end()); getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendChar(' '); buffer->appendText(Quantifier::Stringify(getMember(2)->getIntValue(true))); buffer->appendChar(' '); buffer->appendText((*it).second); buffer->appendChar(' '); getMember(1)->stringify(buffer, verbose, failIfLong); return; } if (type == NODE_TYPE_RANGE) { // not used by V8 TRI_ASSERT(numMembers() == 2); getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendText(TRI_CHAR_LENGTH_PAIR("..")); getMember(1)->stringify(buffer, verbose, failIfLong); return; } std::string message("stringification not supported for node type "); message.append(getTypeString()); THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, message); } std::string AstNode::toString() const { arangodb::basics::StringBuffer buffer(TRI_UNKNOWN_MEM_ZONE); stringify(&buffer, false, false); return std::string(buffer.c_str(), buffer.length()); } /// @brief locate a variable including the direct path vector leading to it. void AstNode::findVariableAccess( std::vector& currentPath, std::vector>& paths, Variable const* findme) const { currentPath.push_back(this); switch (type) { case NODE_TYPE_VARIABLE: case NODE_TYPE_REFERENCE: { auto variable = static_cast(getData()); TRI_ASSERT(variable != nullptr); if (variable->id == findme->id) { paths.emplace_back(currentPath); } } break; case NODE_TYPE_OPERATOR_NARY_AND: case NODE_TYPE_OPERATOR_NARY_OR: case NODE_TYPE_OBJECT: // yes, keys can't be vars, but... case NODE_TYPE_ARRAY: { size_t const n = numMembers(); for (size_t i = 0; (i < n); ++i) { AstNode* member = getMember(i); if (member != nullptr) { member->findVariableAccess(currentPath, paths, findme); } } } break; // One subnode: case NODE_TYPE_FCALL: case NODE_TYPE_ATTRIBUTE_ACCESS: case NODE_TYPE_OPERATOR_UNARY_PLUS: case NODE_TYPE_OPERATOR_UNARY_MINUS: case NODE_TYPE_OPERATOR_UNARY_NOT: getMember(0)->findVariableAccess(currentPath, paths, findme); break; // two subnodes to inspect: case NODE_TYPE_RANGE: case NODE_TYPE_ITERATOR: case NODE_TYPE_ARRAY_LIMIT: case NODE_TYPE_INDEXED_ACCESS: case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS: 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_BINARY_NIN: getMember(0)->findVariableAccess(currentPath, paths, findme); getMember(1)->findVariableAccess(currentPath, paths, findme); break; case NODE_TYPE_EXPANSION: { getMember(0)->findVariableAccess(currentPath, paths, findme); getMember(1)->findVariableAccess(currentPath, paths, findme); auto filterNode = getMember(2); if (filterNode != nullptr) { filterNode->findVariableAccess(currentPath, paths, findme); } auto limitNode = getMember(3); if (limitNode != nullptr) { limitNode->findVariableAccess(currentPath, paths, findme); } auto returnNode = getMember(4); if (returnNode != nullptr) { returnNode->findVariableAccess(currentPath, paths, findme); } } break; // no sub nodes, not a variable: case NODE_TYPE_OPERATOR_TERNARY: case NODE_TYPE_SUBQUERY: case NODE_TYPE_VALUE: 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_OBJECT_ELEMENT: case NODE_TYPE_COLLECTION: case NODE_TYPE_PARAMETER: case NODE_TYPE_FCALL_USER: case NODE_TYPE_NOP: case NODE_TYPE_COLLECT_COUNT: case NODE_TYPE_AGGREGATIONS: case NODE_TYPE_CALCULATED_OBJECT_ELEMENT: case NODE_TYPE_UPSERT: case NODE_TYPE_EXAMPLE: case NODE_TYPE_PASSTHRU: case NODE_TYPE_DISTINCT: case NODE_TYPE_TRAVERSAL: case NODE_TYPE_SHORTEST_PATH: case NODE_TYPE_COLLECTION_LIST: case NODE_TYPE_DIRECTION: case NODE_TYPE_WITH: case NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ: case NODE_TYPE_OPERATOR_BINARY_ARRAY_NE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_LT: case NODE_TYPE_OPERATOR_BINARY_ARRAY_LE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_GT: case NODE_TYPE_OPERATOR_BINARY_ARRAY_GE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_IN: case NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN: case NODE_TYPE_QUANTIFIER: break; } currentPath.pop_back(); } AstNode const* AstNode::findReference(AstNode const* findme) const { if (this == findme) { return this; } const AstNode* ret = nullptr; switch (type) { case NODE_TYPE_OBJECT: // yes, keys can't be vars, but... case NODE_TYPE_ARRAY: { size_t const n = numMembers(); for (size_t i = 0; i < n; ++i) { AstNode* member = getMember(i); if (member == findme) { return this; } if (member != nullptr) { ret = member->findReference(findme); if (ret != nullptr) { return ret; } } } } break; // One subnode: case NODE_TYPE_FCALL: case NODE_TYPE_ATTRIBUTE_ACCESS: case NODE_TYPE_OPERATOR_UNARY_PLUS: case NODE_TYPE_OPERATOR_UNARY_MINUS: case NODE_TYPE_OPERATOR_UNARY_NOT: if (getMember(0) == findme) { return this; } return getMember(0)->findReference(findme); // two subnodes to inspect: case NODE_TYPE_RANGE: case NODE_TYPE_ITERATOR: case NODE_TYPE_ARRAY_LIMIT: case NODE_TYPE_INDEXED_ACCESS: case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS: 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_BINARY_NIN: if (getMember(0) == findme) { return this; } ret = getMember(0)->findReference(findme); if (ret != nullptr) { return ret; } if (getMember(1) == findme) { return this; } ret = getMember(1)->findReference(findme); break; case NODE_TYPE_EXPANSION: { if (getMember(0) == findme) { return this; } ret = getMember(0)->findReference(findme); if (ret != nullptr) { return ret; } if (getMember(1) == findme) { return this; } ret = getMember(1)->findReference(findme); if (ret != nullptr) { return ret; } auto filterNode = getMember(2); if (filterNode != nullptr) { if (filterNode->getMember(0) == findme) { return this; } ret = filterNode->getMember(0)->findReference(findme); if (ret != nullptr) { return ret; } } auto limitNode = getMember(3); if (limitNode != nullptr) { if (limitNode->getMember(0) == findme) { return this; } ret = limitNode->getMember(0)->findReference(findme); if (ret != nullptr) { return ret; } ret = limitNode->getMember(1)->findReference(findme); if (ret != nullptr) { return ret; } } auto returnNode = getMember(4); if (returnNode != nullptr) { if (returnNode->getMember(0) == findme) { return this; } ret = returnNode->getMember(0)->findReference(findme); } } break; // no subnodes here: case NODE_TYPE_OPERATOR_TERNARY: case NODE_TYPE_SUBQUERY: case NODE_TYPE_VALUE: 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_VARIABLE: case NODE_TYPE_REFERENCE: case NODE_TYPE_OBJECT_ELEMENT: case NODE_TYPE_COLLECTION: case NODE_TYPE_PARAMETER: case NODE_TYPE_FCALL_USER: case NODE_TYPE_NOP: case NODE_TYPE_COLLECT_COUNT: case NODE_TYPE_AGGREGATIONS: case NODE_TYPE_CALCULATED_OBJECT_ELEMENT: case NODE_TYPE_UPSERT: case NODE_TYPE_EXAMPLE: case NODE_TYPE_PASSTHRU: case NODE_TYPE_DISTINCT: case NODE_TYPE_TRAVERSAL: case NODE_TYPE_SHORTEST_PATH: case NODE_TYPE_COLLECTION_LIST: case NODE_TYPE_DIRECTION: case NODE_TYPE_OPERATOR_NARY_AND: case NODE_TYPE_OPERATOR_NARY_OR: case NODE_TYPE_WITH: case NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ: case NODE_TYPE_OPERATOR_BINARY_ARRAY_NE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_LT: case NODE_TYPE_OPERATOR_BINARY_ARRAY_LE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_GT: case NODE_TYPE_OPERATOR_BINARY_ARRAY_GE: case NODE_TYPE_OPERATOR_BINARY_ARRAY_IN: case NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN: case NODE_TYPE_QUANTIFIER: break; } return ret; } /// @brief stringify the value of a node into a string buffer /// this creates an equivalent to what JSON.stringify() would do /// this method is used when generated JavaScript code for the node! void AstNode::appendValue(arangodb::basics::StringBuffer* buffer) const { TRI_ASSERT(type == NODE_TYPE_VALUE); switch (value.type) { case VALUE_TYPE_BOOL: { if (value.value._bool) { buffer->appendText(TRI_CHAR_LENGTH_PAIR("true")); } else { buffer->appendText(TRI_CHAR_LENGTH_PAIR("false")); } break; } case VALUE_TYPE_INT: { buffer->appendInteger(value.value._int); break; } case VALUE_TYPE_DOUBLE: { double const v = value.value._double; if (std::isnan(v) || !std::isfinite(v) || v == HUGE_VAL || v == -HUGE_VAL) { buffer->appendText(TRI_CHAR_LENGTH_PAIR("null")); } else { buffer->appendDecimal(value.value._double); } break; } case VALUE_TYPE_STRING: { buffer->appendJsonEncoded(value.value._string, value.length); break; } case VALUE_TYPE_NULL: default: { buffer->appendText(TRI_CHAR_LENGTH_PAIR("null")); break; } } } /// @brief append the AstNode to an output stream std::ostream& operator<<(std::ostream& stream, arangodb::aql::AstNode const* node) { if (node != nullptr) { stream << arangodb::aql::AstNode::toString(node); } return stream; } /// @brief append the AstNode to an output stream std::ostream& operator<<(std::ostream& stream, arangodb::aql::AstNode const& node) { stream << arangodb::aql::AstNode::toString(&node); return stream; }