1
0
Fork 0
arangodb/arangod/Aql/Ast.cpp

2667 lines
87 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief Aql, query AST
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2014 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
/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
/// @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
#include "Aql/Ast.h"
#include "Aql/Arithmetic.h"
#include "Aql/Collection.h"
#include "Aql/Executor.h"
#include "Basics/tri-strings.h"
#include "Basics/Exceptions.h"
#include "VocBase/collection.h"
using namespace triagens::aql;
// -----------------------------------------------------------------------------
// --SECTION-- static initialisation
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief initialise a singleton no-op node instance
////////////////////////////////////////////////////////////////////////////////
AstNode const Ast::NopNode{ NODE_TYPE_NOP };
////////////////////////////////////////////////////////////////////////////////
/// @brief initialise a singleton null node instance
////////////////////////////////////////////////////////////////////////////////
AstNode const Ast::NullNode{ NODE_TYPE_VALUE, VALUE_TYPE_NULL };
////////////////////////////////////////////////////////////////////////////////
/// @brief initialise a singleton false node instance
////////////////////////////////////////////////////////////////////////////////
AstNode const Ast::FalseNode{ false, VALUE_TYPE_BOOL };
////////////////////////////////////////////////////////////////////////////////
/// @brief initialise a singleton true node instance
////////////////////////////////////////////////////////////////////////////////
AstNode const Ast::TrueNode{ true, VALUE_TYPE_BOOL };
////////////////////////////////////////////////////////////////////////////////
/// @brief initialise a singleton zero node instance
////////////////////////////////////////////////////////////////////////////////
AstNode const Ast::ZeroNode{ static_cast<int64_t>(0), VALUE_TYPE_INT };
////////////////////////////////////////////////////////////////////////////////
/// @brief initialise a singleton empty string node instance
////////////////////////////////////////////////////////////////////////////////
AstNode const Ast::EmptyStringNode{ "", 0, VALUE_TYPE_STRING };
////////////////////////////////////////////////////////////////////////////////
/// @brief inverse comparison operators
////////////////////////////////////////////////////////////////////////////////
std::unordered_map<int, AstNodeType> const Ast::NegatedOperators{
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_EQ), NODE_TYPE_OPERATOR_BINARY_NE },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_NE), NODE_TYPE_OPERATOR_BINARY_EQ },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_GT), NODE_TYPE_OPERATOR_BINARY_LE },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_GE), NODE_TYPE_OPERATOR_BINARY_LT },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_LT), NODE_TYPE_OPERATOR_BINARY_GE },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_LE), NODE_TYPE_OPERATOR_BINARY_GT },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_IN), NODE_TYPE_OPERATOR_BINARY_NIN },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_NIN), NODE_TYPE_OPERATOR_BINARY_IN }
};
////////////////////////////////////////////////////////////////////////////////
/// @brief reverse comparison operators
////////////////////////////////////////////////////////////////////////////////
std::unordered_map<int, AstNodeType> const Ast::ReversedOperators{
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_EQ), NODE_TYPE_OPERATOR_BINARY_EQ },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_GT), NODE_TYPE_OPERATOR_BINARY_LT },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_GE), NODE_TYPE_OPERATOR_BINARY_LE },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_LT), NODE_TYPE_OPERATOR_BINARY_GT },
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_LE), NODE_TYPE_OPERATOR_BINARY_GE }
};
// -----------------------------------------------------------------------------
// --SECTION-- constructors / destructors
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief create the AST
////////////////////////////////////////////////////////////////////////////////
Ast::Ast (Query* query)
: _query(query),
_scopes(),
_variables(),
_bindParameters(),
_root(nullptr),
_queries(),
_writeCollection(nullptr),
_functionsMayAccessDocuments(false) {
TRI_ASSERT(_query != nullptr);
startSubQuery();
TRI_ASSERT(_root != nullptr);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief destroy the AST
////////////////////////////////////////////////////////////////////////////////
Ast::~Ast () {
}
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief convert the AST into JSON
/// the caller is responsible for freeing the JSON later
////////////////////////////////////////////////////////////////////////////////
TRI_json_t* Ast::toJson (TRI_memory_zone_t* zone,
bool verbose) const {
TRI_json_t* json = TRI_CreateArrayJson(zone);
try {
_root->toJson(json, zone, verbose);
}
catch (...) {
TRI_FreeJson(zone, json);
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
return json;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief destroy the AST
////////////////////////////////////////////////////////////////////////////////
void Ast::addOperation (AstNode* node) {
TRI_ASSERT(_root != nullptr);
_root->addMember(node);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief find the bottom-most expansion subnodes (if any)
////////////////////////////////////////////////////////////////////////////////
AstNode const* Ast::findExpansionSubNode (AstNode const* current) const {
while (true) {
TRI_ASSERT(current->type == NODE_TYPE_EXPANSION);
if (current->getMember(1)->type != NODE_TYPE_EXPANSION) {
return current;
}
current = current->getMember(1);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST passhthru node
/// note: this type of node is only used during parsing and optimized away later
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodePassthru (AstNode const* what) {
AstNode* node = createNode(NODE_TYPE_PASSTHRU);
node->addMember(what);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST example node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeExample (AstNode const* variable,
AstNode const* example) {
if (example == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
if (example->type != NODE_TYPE_OBJECT && example->type != NODE_TYPE_PARAMETER) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_PARSE, "expecting object literal or bind parameter for example");
}
AstNode* node = createNode(NODE_TYPE_EXAMPLE);
node->setData(const_cast<AstNode*>(variable));
node->addMember(example);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST for node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeFor (char const* variableName,
size_t nameLength,
AstNode const* expression,
bool isUserDefinedVariable) {
if (variableName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_FOR);
AstNode* variable = createNodeVariable(variableName, nameLength, isUserDefinedVariable);
node->addMember(variable);
node->addMember(expression);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST let node, without an IF condition
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeLet (char const* variableName,
size_t nameLength,
AstNode const* expression,
bool isUserDefinedVariable) {
if (variableName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_LET);
AstNode* variable = createNodeVariable(variableName, nameLength, isUserDefinedVariable);
node->addMember(variable);
node->addMember(expression);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST let node, without creating a variable
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeLet (AstNode const* variable,
AstNode const* expression) {
if (variable == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_LET);
node->addMember(variable);
node->addMember(expression);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST let node, with an IF condition
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeLet (char const* variableName,
size_t nameLength,
AstNode const* expression,
AstNode const* condition) {
if (variableName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_LET);
AstNode* variable = createNodeVariable(variableName, nameLength, true);
node->addMember(variable);
node->addMember(expression);
node->addMember(condition);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST filter node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeFilter (AstNode const* expression) {
AstNode* node = createNode(NODE_TYPE_FILTER);
node->addMember(expression);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST filter node for an UPSERT query
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeUpsertFilter (AstNode const* variable,
AstNode const* object) {
AstNode* node = createNode(NODE_TYPE_FILTER);
AstNode* example = createNodeExample(variable, object);
node->addMember(example);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST return node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeReturn (AstNode const* expression) {
AstNode* node = createNode(NODE_TYPE_RETURN);
node->addMember(expression);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST remove node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeRemove (AstNode const* expression,
AstNode const* collection,
AstNode const* options) {
AstNode* node = createNode(NODE_TYPE_REMOVE);
if (options == nullptr) {
// no options given. now use default options
options = &NopNode;
}
node->addMember(options);
node->addMember(collection);
node->addMember(expression);
node->addMember(createNodeVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_OLD), false));
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST insert node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeInsert (AstNode const* expression,
AstNode const* collection,
AstNode const* options) {
AstNode* node = createNode(NODE_TYPE_INSERT);
if (options == nullptr) {
// no options given. now use default options
options = &NopNode;
}
node->addMember(options);
node->addMember(collection);
node->addMember(expression);
node->addMember(createNodeVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_NEW), false));
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST update node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeUpdate (AstNode const* keyExpression,
AstNode const* docExpression,
AstNode const* collection,
AstNode const* options) {
AstNode* node = createNode(NODE_TYPE_UPDATE);
if (options == nullptr) {
// no options given. now use default options
options = &NopNode;
}
node->addMember(options);
node->addMember(collection);
node->addMember(docExpression);
if (keyExpression != nullptr) {
node->addMember(keyExpression);
}
else {
node->addMember(&NopNode);
}
node->addMember(createNodeVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_OLD), false));
node->addMember(createNodeVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_NEW), false));
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST replace node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeReplace (AstNode const* keyExpression,
AstNode const* docExpression,
AstNode const* collection,
AstNode const* options) {
AstNode* node = createNode(NODE_TYPE_REPLACE);
if (options == nullptr) {
// no options given. now use default options
options = &NopNode;
}
node->addMember(options);
node->addMember(collection);
node->addMember(docExpression);
if (keyExpression != nullptr) {
node->addMember(keyExpression);
}
else {
node->addMember(&NopNode);
}
node->addMember(createNodeVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_OLD), false));
node->addMember(createNodeVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_NEW), false));
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST upsert node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeUpsert (AstNodeType type,
AstNode const* docVariable,
AstNode const* insertExpression,
AstNode const* updateExpression,
AstNode const* collection,
AstNode const* options) {
AstNode* node = createNode(NODE_TYPE_UPSERT);
node->setIntValue(static_cast<int64_t>(type));
if (options == nullptr) {
// no options given. now use default options
options = &NopNode;
}
node->addMember(options);
node->addMember(collection);
node->addMember(docVariable);
node->addMember(insertExpression);
node->addMember(updateExpression);
node->addMember(createNodeReference(Variable::NAME_OLD));
node->addMember(createNodeVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_NEW), false));
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST distinct node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeDistinct (AstNode const* value) {
AstNode* node = createNode(NODE_TYPE_DISTINCT);
node->addMember(value);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST collect node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeCollect (AstNode const* list,
char const* name,
size_t nameLength,
AstNode const* keepVariables,
AstNode const* options) {
AstNode* node = createNode(NODE_TYPE_COLLECT);
if (options == nullptr) {
// no options given. now use default options
options = &NopNode;
}
node->addMember(options);
node->addMember(list);
// INTO
if (name != nullptr) {
AstNode* variable = createNodeVariable(name, nameLength, true);
node->addMember(variable);
// KEEP
if (keepVariables != nullptr) {
node->addMember(keepVariables);
}
}
else {
TRI_ASSERT(keepVariables == nullptr);
TRI_ASSERT(nameLength == 0);
}
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST collect node, INTO var = expr
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeCollectExpression (AstNode const* list,
char const* name,
size_t nameLength,
AstNode const* expression,
AstNode const* options) {
AstNode* node = createNode(NODE_TYPE_COLLECT_EXPRESSION);
if (options == nullptr) {
// no options given. now use default options
options = &NopNode;
}
node->addMember(options);
node->addMember(list);
AstNode* variable = createNodeVariable(name, nameLength, true);
node->addMember(variable);
node->addMember(expression);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST collect node, COUNT INTO
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeCollectCount (AstNode const* list,
char const* name,
size_t nameLength,
AstNode const* options) {
AstNode* node = createNode(NODE_TYPE_COLLECT_COUNT);
if (options == nullptr) {
// no options given. now use default options
options = &NopNode;
}
node->addMember(options);
node->addMember(list);
AstNode* variable = createNodeVariable(name, nameLength, true);
node->addMember(variable);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST sort node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeSort (AstNode const* list) {
AstNode* node = createNode(NODE_TYPE_SORT);
node->addMember(list);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST sort element node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeSortElement (AstNode const* expression,
AstNode const* ascending) {
AstNode* node = createNode(NODE_TYPE_SORT_ELEMENT);
node->addMember(expression);
node->addMember(ascending);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST limit node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeLimit (AstNode const* offset,
AstNode const* count) {
AstNode* node = createNode(NODE_TYPE_LIMIT);
node->addMember(offset);
node->addMember(count);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST assign node, used in COLLECT statements
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeAssign (char const* variableName,
size_t nameLength,
AstNode const* expression) {
if (variableName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_ASSIGN);
AstNode* variable = createNodeVariable(variableName, nameLength, true);
node->addMember(variable);
node->addMember(expression);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST variable node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeVariable (char const* name,
size_t nameLength,
bool isUserDefined) {
if (name == nullptr || nameLength == 0) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
if (isUserDefined && *name == '_') {
_query->registerError(TRI_ERROR_QUERY_VARIABLE_NAME_INVALID, name);
return nullptr;
}
if (_scopes.existsVariable(name, nameLength)) {
_query->registerError(TRI_ERROR_QUERY_VARIABLE_REDECLARED, name);
return nullptr;
}
auto variable = _variables.createVariable(name, nameLength, isUserDefined);
_scopes.addVariable(variable);
AstNode* node = createNode(NODE_TYPE_VARIABLE);
node->setData(static_cast<void*>(variable));
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST collection node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeCollection (char const* name,
TRI_transaction_type_e accessType) {
if (name == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
if (*name == '\0' || ! TRI_IsAllowedNameCollection(true, name)) {
_query->registerErrorCustom(TRI_ERROR_ARANGO_ILLEGAL_NAME, name);
return nullptr;
}
AstNode* node = createNode(NODE_TYPE_COLLECTION);
node->setStringValue(name, strlen(name));
_query->collections()->add(name, accessType);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST reference node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeReference (char const* variableName,
size_t nameLength) {
if (variableName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_REFERENCE);
auto variable = _scopes.getVariable(std::string(variableName, nameLength));
if (variable == nullptr) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "variable not found in reference AstNode");
}
node->setData(variable);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST reference node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeReference (std::string const& variableName) {
AstNode* node = createNode(NODE_TYPE_REFERENCE);
auto variable = _scopes.getVariable(variableName);
if (variable == nullptr) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "variable not found in reference AstNode");
}
node->setData(variable);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST reference node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeReference (Variable const* variable) {
AstNode* node = createNode(NODE_TYPE_REFERENCE);
node->setData(variable);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST parameter node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeParameter (char const* name,
size_t length) {
if (name == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_PARAMETER);
node->setStringValue(name, length);
// insert bind parameter name into list of found parameters
_bindParameters.emplace(name);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST unary operator node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeUnaryOperator (AstNodeType type,
AstNode const* operand) {
AstNode* node = createNode(type);
node->addMember(operand);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST binary operator node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeBinaryOperator (AstNodeType type,
AstNode const* lhs,
AstNode const* rhs) {
AstNode* node = createNode(type);
node->addMember(lhs);
node->addMember(rhs);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST ternary operator node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeTernaryOperator (AstNode const* condition,
AstNode const* truePart,
AstNode const* falsePart) {
AstNode* node = createNode(NODE_TYPE_OPERATOR_TERNARY);
node->addMember(condition);
node->addMember(truePart);
node->addMember(falsePart);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST attribute access node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeAttributeAccess (AstNode const* accessed,
char const* attributeName,
size_t nameLength) {
if (attributeName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_ATTRIBUTE_ACCESS);
node->addMember(accessed);
node->setStringValue(attributeName, nameLength);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST attribute access node w/ bind parameter
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeBoundAttributeAccess (AstNode const* accessed,
AstNode const* parameter) {
AstNode* node = createNode(NODE_TYPE_BOUND_ATTRIBUTE_ACCESS);
node->setStringValue(parameter->getStringValue(), parameter->getStringLength());
node->addMember(accessed);
node->addMember(parameter);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST indexed access node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeIndexedAccess (AstNode const* accessed,
AstNode const* indexValue) {
AstNode* node = createNode(NODE_TYPE_INDEXED_ACCESS);
node->addMember(accessed);
node->addMember(indexValue);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST array limit node (offset, count)
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeArrayLimit (AstNode const* offset,
AstNode const* count) {
AstNode* node = createNode(NODE_TYPE_ARRAY_LIMIT);
if (offset == nullptr) {
offset = createNodeValueInt(0);
}
node->addMember(offset);
node->addMember(count);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST expansion node, with or without a filter
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeExpansion (int64_t levels,
AstNode const* iterator,
AstNode const* expanded,
AstNode const* filter,
AstNode const* limit,
AstNode const* projection) {
AstNode* node = createNode(NODE_TYPE_EXPANSION);
node->setIntValue(levels);
node->addMember(iterator);
node->addMember(expanded);
if (filter == nullptr) {
node->addMember(createNodeNop());
}
else {
node->addMember(filter);
}
if (limit == nullptr) {
node->addMember(createNodeNop());
}
else {
TRI_ASSERT(limit->type == NODE_TYPE_ARRAY_LIMIT);
node->addMember(limit);
}
if (projection == nullptr) {
node->addMember(createNodeNop());
}
else {
node->addMember(projection);
}
TRI_ASSERT(node->numMembers() == 5);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST iterator node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeIterator (char const* variableName,
size_t nameLength,
AstNode const* expanded) {
if (variableName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_ITERATOR);
AstNode* variable = createNodeVariable(variableName, nameLength, false);
node->addMember(variable);
node->addMember(expanded);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST null value node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeValueNull () {
// performance optimization:
// return a pointer to the singleton null node
// note: this node is never registered nor freed
return const_cast<AstNode*>(&NullNode);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST bool value node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeValueBool (bool value) {
// performance optimization:
// return a pointer to the singleton bool nodes
// note: these nodes are never registered nor freed
if (value) {
return const_cast<AstNode*>(&TrueNode);
}
return const_cast<AstNode*>(&FalseNode);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST int value node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeValueInt (int64_t value) {
if (value == 0) {
// performance optimization:
// return a pointer to the singleton zero node
// note: these nodes are never registered nor freed
return const_cast<AstNode*>(&ZeroNode);
}
AstNode* node = createNode(NODE_TYPE_VALUE);
node->setValueType(VALUE_TYPE_INT);
node->setIntValue(value);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST double value node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeValueDouble (double value) {
AstNode* node = createNode(NODE_TYPE_VALUE);
node->setValueType(VALUE_TYPE_DOUBLE);
node->setDoubleValue(value);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST string value node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeValueString (char const* value,
size_t length) {
if (value == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
if (*value == '\0') {
// performance optimization:
// return a pointer to the singleton empty string node
// note: these nodes are never registered nor freed
return const_cast<AstNode*>(&EmptyStringNode);
}
AstNode* node = createNode(NODE_TYPE_VALUE);
node->setValueType(VALUE_TYPE_STRING);
node->setStringValue(value, length);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST array node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeArray () {
AstNode* node = createNode(NODE_TYPE_ARRAY);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST object node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeObject () {
return createNode(NODE_TYPE_OBJECT);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST object element node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeObjectElement (char const* attributeName,
size_t nameLength,
AstNode const* expression) {
if (attributeName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_OBJECT_ELEMENT);
node->setStringValue(attributeName, nameLength);
node->addMember(expression);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST calculated object element node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeCalculatedObjectElement (AstNode const* attributeName,
AstNode const* expression) {
AstNode* node = createNode(NODE_TYPE_CALCULATED_OBJECT_ELEMENT);
node->addMember(attributeName);
node->addMember(expression);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST function call node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeFunctionCall (char const* functionName,
AstNode const* arguments) {
if (functionName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
auto normalized = normalizeFunctionName(functionName);
AstNode* node;
if (normalized.second) {
// built-in function
node = createNode(NODE_TYPE_FCALL);
// register a pointer to the function
auto func = _query->executor()->getFunctionByName(normalized.first);
TRI_ASSERT(func != nullptr);
node->setData(static_cast<void const*>(func));
TRI_ASSERT(arguments != nullptr);
TRI_ASSERT(arguments->type == NODE_TYPE_ARRAY);
// validate number of function call arguments
size_t const n = arguments->numMembers();
auto numExpectedArguments = func->numArguments();
if (n < numExpectedArguments.first || n > numExpectedArguments.second) {
THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH,
functionName,
static_cast<int>(numExpectedArguments.first),
static_cast<int>(numExpectedArguments.second));
}
if (! func->canRunOnDBServer) {
// this also qualifies a query for potentially reading or modifying documents via function calls!
_functionsMayAccessDocuments = true;
}
}
else {
// user-defined function
node = createNode(NODE_TYPE_FCALL_USER);
// register the function name
char* fname = _query->registerString(normalized.first);
node->setStringValue(fname, normalized.first.size());
_functionsMayAccessDocuments = true;
}
node->addMember(arguments);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST range node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeRange (AstNode const* start,
AstNode const* end) {
AstNode* node = createNode(NODE_TYPE_RANGE);
node->addMember(start);
node->addMember(end);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST nop node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeNop () {
return const_cast<AstNode*>(&NopNode);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief injects bind parameters into the AST
////////////////////////////////////////////////////////////////////////////////
void Ast::injectBindParameters (BindParameters& parameters) {
auto p = parameters();
auto func = [&](AstNode* node, void*) -> AstNode* {
if (node->type == NODE_TYPE_PARAMETER) {
// found a bind parameter in the query string
char const* param = node->getStringValue();
size_t const length = node->getStringLength();
if (param == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
auto it = p.find(std::string(param, length));
if (it == p.end()) {
// query uses a bind parameter that was not defined by the user
_query->registerError(TRI_ERROR_QUERY_BIND_PARAMETER_MISSING, param);
return nullptr;
}
// mark the bind parameter as being used
(*it).second.second = true;
auto value = (*it).second.first;
if (*param == '@') {
// collection parameter
TRI_ASSERT(TRI_IsStringJson(value));
bool isWriteCollection = false;
if (_writeCollection != nullptr &&
_writeCollection->type == NODE_TYPE_PARAMETER &&
::strcmp(param, _writeCollection->getStringValue()) == 0) {
isWriteCollection = true;
}
// turn node into a collection node
size_t const length = value->_value._string.length - 1;
char const* name = _query->registerString(value->_value._string.data, length);
node = createNodeCollection(name, isWriteCollection ? TRI_TRANSACTION_WRITE : TRI_TRANSACTION_READ);
if (isWriteCollection) {
// this was the bind parameter that contained the collection to update
_writeCollection = node;
}
}
else {
node = nodeFromJson(value, false);
if (node != nullptr) {
// already mark node as constant here
node->setFlag(DETERMINED_CONSTANT, VALUE_CONSTANT);
// mark node as simple
node->setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE);
// mark node as executable on db-server
node->setFlag(DETERMINED_RUNONDBSERVER, VALUE_RUNONDBSERVER);
// mark node as non-throwing
node->setFlag(DETERMINED_THROWS);
// mark node as deterministic
node->setFlag(DETERMINED_NONDETERMINISTIC);
// finally note that the node was created from a bind parameter
node->setFlag(FLAG_BIND_PARAMETER);
}
}
}
else if (node->type == NODE_TYPE_BOUND_ATTRIBUTE_ACCESS) {
// look at second sub-node. this is the (replaced) bind parameter
auto name = node->getMember(1);
if (name->type != NODE_TYPE_VALUE ||
name->value.type != VALUE_TYPE_STRING ||
name->value.length == 0) {
// if no string value was inserted for the parameter name, this is an error
THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_BIND_PARAMETER_TYPE, node->getStringValue());
}
// convert into a regular attribute access node to simplify handling later
return createNodeAttributeAccess(node->getMember(0), name->getStringValue(), name->getStringLength());
}
return node;
};
_root = traverseAndModify(_root, func, &p);
if (_writeCollection != nullptr &&
_writeCollection->type == NODE_TYPE_COLLECTION) {
_query->collections()->add(_writeCollection->getStringValue(), TRI_TRANSACTION_WRITE);
}
for (auto it = p.begin(); it != p.end(); ++it) {
if (! (*it).second.second) {
THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_BIND_PARAMETER_UNDECLARED, (*it).first.c_str());
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replace variables
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::replaceVariables (AstNode* node,
std::unordered_map<VariableId, Variable const*> const& replacements) {
auto visitor = [&](AstNode* node, void*) -> AstNode* {
if (node == nullptr) {
return nullptr;
}
// reference to a variable
if (node->type == NODE_TYPE_REFERENCE) {
auto variable = static_cast<Variable*>(node->getData());
if (variable != nullptr) {
auto it = replacements.find(variable->id);
if (it != replacements.end()) {
// overwrite the node in place
node->setData((*it).second);
}
}
// fall-through intentional
}
return node;
};
return traverseAndModify(node, visitor, nullptr);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replace a variable reference in the expression with another
/// expression (e.g. inserting c = `a + b` into expression `c + 1` so the latter
/// becomes `a + b + 1`
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::replaceVariableReference (AstNode* node,
Variable const* variable,
AstNode const* expressionNode) {
auto visitor = [&](AstNode* node, void*) -> AstNode* {
if (node == nullptr) {
return nullptr;
}
// reference to a variable
if (node->type == NODE_TYPE_REFERENCE &&
static_cast<Variable const*>(node->getData()) == variable) {
// found the target node. now insert the new node
return const_cast<AstNode*>(expressionNode);
}
return node;
};
return traverseAndModify(node, visitor, nullptr);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the AST
/// this does not only optimize but also performs a few validations after
/// bind parameter injection. merging this pass with the regular AST
/// optimizations saves one extra pass over the AST
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
void Ast::validateAndOptimize () {
struct TraversalContext {
int64_t stopOptimizationRequests = 0;
bool isInFilter = false;
bool hasSeenWriteNode = false;
};
auto preVisitor = [&](AstNode const* node, void* data) -> bool {
if (node->type == NODE_TYPE_FILTER) {
static_cast<TraversalContext*>(data)->isInFilter = true;
}
else if (node->type == NODE_TYPE_FCALL) {
auto func = static_cast<Function*>(node->getData());
TRI_ASSERT(func != nullptr);
if (func->externalName == "NOOPT") {
// NOOPT will turn all function optimizations off
++(static_cast<TraversalContext*>(data)->stopOptimizationRequests);
}
}
else if (node->hasFlag(FLAG_BIND_PARAMETER)) {
return false;
}
return true;
};
auto postVisitor = [&](AstNode const* node, void* data) -> void {
if (node->type == NODE_TYPE_FILTER) {
static_cast<TraversalContext*>(data)->isInFilter = false;
}
if (node->type == NODE_TYPE_REMOVE ||
node->type == NODE_TYPE_INSERT ||
node->type == NODE_TYPE_UPDATE ||
node->type == NODE_TYPE_REPLACE ||
node->type == NODE_TYPE_UPSERT) {
static_cast<TraversalContext*>(data)->hasSeenWriteNode = true;
}
else if (node->type == NODE_TYPE_FCALL) {
auto func = static_cast<Function*>(node->getData());
TRI_ASSERT(func != nullptr);
if (func->externalName == "NOOPT") {
// NOOPT will turn all function optimizations off
--(static_cast<TraversalContext*>(data)->stopOptimizationRequests);
}
}
};
auto visitor = [&](AstNode* node, void* data) -> AstNode* {
if (node == nullptr) {
return nullptr;
}
// unary operators
if (node->type == NODE_TYPE_OPERATOR_UNARY_PLUS ||
node->type == NODE_TYPE_OPERATOR_UNARY_MINUS) {
return this->optimizeUnaryOperatorArithmetic(node);
}
if (node->type == NODE_TYPE_OPERATOR_UNARY_NOT) {
return this->optimizeUnaryOperatorLogical(node);
}
// binary operators
if (node->type == NODE_TYPE_OPERATOR_BINARY_AND ||
node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
return this->optimizeBinaryOperatorLogical(node, static_cast<TraversalContext*>(data)->isInFilter);
}
if (node->type == NODE_TYPE_OPERATOR_BINARY_EQ ||
node->type == NODE_TYPE_OPERATOR_BINARY_NE ||
node->type == NODE_TYPE_OPERATOR_BINARY_LT ||
node->type == NODE_TYPE_OPERATOR_BINARY_LE ||
node->type == NODE_TYPE_OPERATOR_BINARY_GT ||
node->type == NODE_TYPE_OPERATOR_BINARY_GE ||
node->type == NODE_TYPE_OPERATOR_BINARY_IN ||
node->type == NODE_TYPE_OPERATOR_BINARY_NIN) {
return this->optimizeBinaryOperatorRelational(node);
}
if (node->type == NODE_TYPE_OPERATOR_BINARY_PLUS ||
node->type == NODE_TYPE_OPERATOR_BINARY_MINUS ||
node->type == NODE_TYPE_OPERATOR_BINARY_TIMES ||
node->type == NODE_TYPE_OPERATOR_BINARY_DIV ||
node->type == NODE_TYPE_OPERATOR_BINARY_MOD) {
return this->optimizeBinaryOperatorArithmetic(node);
}
// ternary operator
if (node->type == NODE_TYPE_OPERATOR_TERNARY) {
return this->optimizeTernaryOperator(node);
}
// passthru node
if (node->type == NODE_TYPE_PASSTHRU) {
// optimize away passthru node. this type of node is only used during parsing
return node->getMember(0);
}
// call to built-in function
if (node->type == NODE_TYPE_FCALL) {
auto func = static_cast<Function*>(node->getData());
if (static_cast<TraversalContext*>(data)->hasSeenWriteNode &&
! func->canRunOnDBServer) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_ACCESS_AFTER_MODIFICATION);
}
if (static_cast<TraversalContext*>(data)->stopOptimizationRequests == 0) {
// optimization allowed
return this->optimizeFunctionCall(node);
}
// optimization not allowed
return node;
}
// reference to a variable, may be able to insert the variable value directly
if (node->type == NODE_TYPE_REFERENCE) {
return this->optimizeReference(node);
}
// indexed access, e.g. a[0] or a['foo']
if (node->type == NODE_TYPE_INDEXED_ACCESS) {
return this->optimizeIndexedAccess(node);
}
// LET
if (node->type == NODE_TYPE_LET) {
return this->optimizeLet(node);
}
// FILTER
if (node->type == NODE_TYPE_FILTER) {
return this->optimizeFilter(node);
}
// FOR
if (node->type == NODE_TYPE_FOR) {
return this->optimizeFor(node);
}
// collection
if (node->type == NODE_TYPE_COLLECTION) {
if (static_cast<TraversalContext*>(data)->hasSeenWriteNode) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_ACCESS_AFTER_MODIFICATION);
}
return node;
}
// example
if (node->type == NODE_TYPE_EXAMPLE) {
return this->makeConditionFromExample(node);
}
return node;
};
// run the optimizations
TraversalContext context;
this->_root = traverseAndModify(this->_root, preVisitor, visitor, postVisitor, &context);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief determines the variables referenced in an expression
////////////////////////////////////////////////////////////////////////////////
void Ast::getReferencedVariables (AstNode const* node,
std::unordered_set<Variable const*>& result) {
auto visitor = [](AstNode const* node, void* data) -> void {
if (node == nullptr) {
return;
}
// reference to a variable
if (node->type == NODE_TYPE_REFERENCE) {
auto variable = static_cast<Variable const*>(node->getData());
if (variable == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
if (variable->needsRegister()) {
auto result = static_cast<std::unordered_set<Variable const*>*>(data);
result->emplace(variable);
}
}
};
traverseReadOnly(node, visitor, &result);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief determines the top-level attributes referenced in an expression,
/// grouped by variable name
////////////////////////////////////////////////////////////////////////////////
TopLevelAttributes Ast::getReferencedAttributes (AstNode const* node,
bool& isSafeForOptimization) {
TopLevelAttributes result;
auto doNothingVisitor = [](AstNode const* node, void* data) -> void { };
// traversal state
char const* attributeName = nullptr;
size_t nameLength = 0;
isSafeForOptimization = true;
auto visitor = [&](AstNode const* node, void* data) -> void {
if (node == nullptr) {
return;
}
if (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
attributeName = node->getStringValue();
nameLength = node->getStringLength();
return;
}
if (node->type == NODE_TYPE_REFERENCE) {
// reference to a variable
if (attributeName == nullptr) {
// we haven't seen an attribute access directly before...
// this may have been an access to an indexed property, e.g value[0] or a
// reference to the complete value, e.g. FUNC(value)
// note that this is unsafe to optimize this away
isSafeForOptimization = false;
return;
}
TRI_ASSERT(attributeName != nullptr);
auto variable = static_cast<Variable const*>(node->getData());
if (variable == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
auto it = result.find(variable);
if (it == result.end()) {
// insert variable and attributeName
result.emplace(variable, std::unordered_set<std::string>({ std::string(attributeName, nameLength) }));
}
else {
// insert attributeName only
(*it).second.emplace(std::string(attributeName, nameLength));
}
// fall-through
}
attributeName = nullptr;
nameLength = 0;
};
traverseReadOnly(node, visitor, doNothingVisitor, doNothingVisitor, nullptr);
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief recursively clone a node
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::clone (AstNode const* node) {
auto type = node->type;
auto copy = createNode(type);
// special handling for certain node types
// copy payload...
if (type == NODE_TYPE_COLLECTION ||
type == NODE_TYPE_PARAMETER ||
type == NODE_TYPE_ATTRIBUTE_ACCESS ||
type == NODE_TYPE_OBJECT_ELEMENT ||
type == NODE_TYPE_FCALL_USER) {
copy->setStringValue(node->getStringValue(), node->getStringLength());
}
else if (type == NODE_TYPE_VARIABLE ||
type == NODE_TYPE_REFERENCE ||
type == NODE_TYPE_FCALL) {
copy->setData(node->getData());
}
else if (type == NODE_TYPE_UPSERT) {
copy->setIntValue(node->getIntValue());
}
else if (type == NODE_TYPE_VALUE) {
switch (node->value.type) {
case VALUE_TYPE_NULL:
copy->value.type = VALUE_TYPE_NULL;
break;
case VALUE_TYPE_BOOL:
copy->value.type = VALUE_TYPE_BOOL;
copy->setBoolValue(node->getBoolValue());
break;
case VALUE_TYPE_INT:
copy->value.type = VALUE_TYPE_INT;
copy->setIntValue(node->getIntValue());
break;
case VALUE_TYPE_DOUBLE:
copy->value.type = VALUE_TYPE_DOUBLE;
copy->setDoubleValue(node->getDoubleValue());
break;
case VALUE_TYPE_STRING:
copy->value.type = VALUE_TYPE_STRING;
copy->setStringValue(node->getStringValue(), node->getStringLength());
break;
}
}
// recursively clone subnodes
size_t const n = node->numMembers();
for (size_t i = 0; i < n; ++i) {
copy->addMember(clone(node->getMemberUnchecked(i)));
}
return copy;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the reversed operator for a comparison operator
////////////////////////////////////////////////////////////////////////////////
AstNodeType Ast::ReverseOperator (AstNodeType type) {
auto it = ReversedOperators.find(static_cast<int>(type));
if (it == ReversedOperators.end()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "invalid node type for inversed operator");
}
return (*it).second;
}
// -----------------------------------------------------------------------------
// --SECTION-- private methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief make condition from example
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::makeConditionFromExample (AstNode const* node) {
TRI_ASSERT(node->numMembers() == 1);
auto object = node->getMember(0);
if (object->type != NODE_TYPE_OBJECT) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_PARSE, "expecting object literal for example");
}
auto variable = static_cast<AstNode*>(node->getData());
if (variable == nullptr) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "internal error in object literal handling");
}
AstNode* result = nullptr;
std::vector<std::pair<char const*, size_t>> attributeParts{ };
std::function<void(AstNode const*)> createCondition = [&] (AstNode const* object) -> void {
TRI_ASSERT(object->type == NODE_TYPE_OBJECT);
auto const n = object->numMembers();
for (size_t i = 0; i < n; ++i) {
auto member = object->getMember(i);
if (member->type != NODE_TYPE_OBJECT_ELEMENT) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_PARSE, "expecting object literal with literal attribute names in example");
}
attributeParts.emplace_back(std::make_pair(member->getStringValue(), member->getStringLength()));
auto value = member->getMember(0);
if (value->type == NODE_TYPE_OBJECT) {
createCondition(value);
}
else {
auto access = variable;
for (auto const& it : attributeParts) {
access = createNodeAttributeAccess(access, it.first, it.second);
}
auto condition = createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, access, value);
if (result == nullptr) {
result = condition;
}
else {
// AND-combine with previous condition
result = createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_AND, result, condition);
}
}
attributeParts.pop_back();
}
};
createCondition(object);
if (result == nullptr) {
result = createNodeValueBool(true);
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a number node for an arithmetic result, integer
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createArithmeticResultNode (int64_t value) {
return createNodeValueInt(value);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a number node for an arithmetic result, double
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createArithmeticResultNode (double value) {
if (value != value || // intentional!
value == HUGE_VAL ||
value == - HUGE_VAL) {
// IEEE754 NaN values have an interesting property that we can exploit...
// if the architecture does not use IEEE754 values then this shouldn't do
// any harm either
_query->registerWarning(TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE);
return createNodeValueNull();
}
return createNodeValueDouble(value);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes an expression with constant parameters
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::executeConstExpression (AstNode const* node) {
// must enter v8 before we can execute any expression
_query->enterContext();
ISOLATE;
v8::HandleScope scope(isolate); // do not delete this!
TRI_json_t* result = _query->executor()->executeExpression(_query, node);
// context is not left here, but later
// this allows re-using the same context for multiple expressions
if (result == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* value = nullptr;
try {
value = nodeFromJson(result, true);
}
catch (...) {
}
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, result);
if (value == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
return value;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the unary operators + and -
/// the unary plus will be converted into a simple value node if the operand of
/// the operation is a constant number
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeUnaryOperatorArithmetic (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_OPERATOR_UNARY_PLUS ||
node->type == NODE_TYPE_OPERATOR_UNARY_MINUS);
TRI_ASSERT(node->numMembers() == 1);
AstNode* operand = node->getMember(0);
if (! operand->isConstant()) {
// operand is dynamic, cannot statically optimize it
return node;
}
// operand is a constant, now convert it into a number
auto converted = operand->castToNumber(this);
if (converted->isNullValue()) {
return createNodeValueNull();
}
if (node->type == NODE_TYPE_OPERATOR_UNARY_PLUS) {
// + number => number
return converted;
}
else {
// - number
if (converted->value.type == VALUE_TYPE_INT) {
// int64
return createNodeValueInt(- converted->getIntValue());
}
else {
// double
double const value = - converted->getDoubleValue();
if (value != value || // intentional
value == HUGE_VAL ||
value == - HUGE_VAL) {
// IEEE754 NaN values have an interesting property that we can exploit...
// if the architecture does not use IEEE754 values then this shouldn't do
// any harm either
return createNodeValueNull();
}
return createNodeValueDouble(value);
}
}
TRI_ASSERT(false);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the unary operator NOT
/// the unary NOT operation will be replaced with the result of the operation
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeNotExpression (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_OPERATOR_UNARY_NOT);
TRI_ASSERT(node->numMembers() == 1);
AstNode* operand = node->getMember(0);
if (operand->isComparisonOperator()) {
// remove the NOT and reverse the operation, e.g. NOT (a == b) => (a != b)
TRI_ASSERT(operand->numMembers() == 2);
auto lhs = operand->getMember(0);
auto rhs = operand->getMember(1);
auto it = NegatedOperators.find(static_cast<int>(operand->type));
TRI_ASSERT(it != NegatedOperators.end());
return createNodeBinaryOperator((*it).second, lhs, rhs);
}
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the unary operator NOT
/// the unary NOT operation will be replaced with the result of the operation
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeUnaryOperatorLogical (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_OPERATOR_UNARY_NOT);
TRI_ASSERT(node->numMembers() == 1);
AstNode* operand = node->getMember(0);
if (! operand->isConstant()) {
// operand is dynamic, cannot statically optimize it
return optimizeNotExpression(node);
}
auto converted = operand->castToBool(this);
// replace unary negation operation with result of negation
return createNodeValueBool(! converted->getBoolValue());
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the binary logical operators && and ||
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeBinaryOperatorLogical (AstNode* node,
bool canModifyResultType) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_OPERATOR_BINARY_AND ||
node->type == NODE_TYPE_OPERATOR_BINARY_OR);
TRI_ASSERT(node->numMembers() == 2);
auto lhs = node->getMember(0);
auto rhs = node->getMember(1);
if (lhs == nullptr || rhs == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
if (lhs->isConstant()) {
// left operand is a constant value
if (node->type == NODE_TYPE_OPERATOR_BINARY_AND) {
if (lhs->isFalse()) {
// return it if it is falsey
return lhs;
}
// left-operand was trueish, now return right operand
return rhs;
}
else if (node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
if (lhs->isTrue()) {
// return it if it is trueish
return lhs;
}
// left-operand was falsey, now return right operand
return rhs;
}
}
if (canModifyResultType) {
if (rhs->isConstant() && ! lhs->canThrow()) {
// right operand is a constant value
if (node->type == NODE_TYPE_OPERATOR_BINARY_AND) {
if (rhs->isFalse()) {
return createNodeValueBool(false);
}
// right-operand was trueish, now return it
return lhs;
}
else if (node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
if (rhs->isTrue()) {
// return it if it is trueish
return createNodeValueBool(true);
}
// right-operand was falsey, now return left operand
return lhs;
}
}
}
// default case
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the binary relational operators <, <=, >, >=, ==, != and IN
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeBinaryOperatorRelational (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 2);
AstNode* lhs = node->getMember(0);
AstNode* rhs = node->getMember(1);
if (lhs == nullptr || rhs == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
bool const lhsIsConst = lhs->isConstant();
bool const rhsIsConst = rhs->isConstant();
if (! lhs->canThrow() &&
rhs->type == NODE_TYPE_ARRAY &&
rhs->numMembers() <= 1 &&
(node->type == NODE_TYPE_OPERATOR_BINARY_IN ||
node->type == NODE_TYPE_OPERATOR_BINARY_NIN)) {
// turn an IN or a NOT IN with few members into an equality comparison
if (rhs->numMembers() == 0) {
// IN with no members returns false
// NOT IN with no members returns true
return createNodeValueBool(node->type == NODE_TYPE_OPERATOR_BINARY_NIN);
}
else if (rhs->numMembers() == 1) {
// IN with a single member becomes equality
// NOT IN with a single members becomes unequality
if (node->type == NODE_TYPE_OPERATOR_BINARY_IN) {
node = createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, lhs, rhs->getMember(0));
}
else {
node = createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_NE, lhs, rhs->getMember(0));
}
// and optimize ourselves...
return optimizeBinaryOperatorRelational(node);
}
// fall-through intentional
}
if (! rhsIsConst) {
return node;
}
if (rhs->type != NODE_TYPE_ARRAY &&
(node->type == NODE_TYPE_OPERATOR_BINARY_IN ||
node->type == NODE_TYPE_OPERATOR_BINARY_NIN)) {
// right operand of IN or NOT IN must be an array, otherwise we return false
return createNodeValueBool(false);
}
if (! lhsIsConst) {
if (rhs->numMembers() >= 10 &&
(node->type == NODE_TYPE_OPERATOR_BINARY_IN ||
node->type == NODE_TYPE_OPERATOR_BINARY_NIN)) {
// if the IN list contains a considerable amount of items, we will sort
// it, so we can find elements quicker later using a binary search
// note that sorting will also set a flag for the node
rhs->sort();
}
return node;
}
return executeConstExpression(node);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the binary arithmetic operators +, -, *, / and %
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeBinaryOperatorArithmetic (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 2);
AstNode* lhs = node->getMember(0);
AstNode* rhs = node->getMember(1);
if (lhs == nullptr || rhs == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
if (lhs->isConstant() && rhs->isConstant()) {
// now calculate the expression result
if (node->type == NODE_TYPE_OPERATOR_BINARY_PLUS) {
// arithmetic +
auto left = lhs->castToNumber(this);
auto right = rhs->castToNumber(this);
if (left->isNullValue() && ! lhs->isNullValue()) {
// conversion of lhs failed
return createNodeValueNull();
}
if (right->isNullValue() && ! rhs->isNullValue()) {
// conversion of rhs failed
return createNodeValueNull();
}
bool useDoublePrecision = (left->isDoubleValue() || right->isDoubleValue());
if (! useDoublePrecision) {
auto l = left->getIntValue();
auto r = right->getIntValue();
// check if the result would overflow
useDoublePrecision = IsUnsafeAddition<int64_t>(l, r);
if (! useDoublePrecision) {
// can calculate using integers
return createArithmeticResultNode(l + r);
}
}
// must use double precision
return createArithmeticResultNode(left->getDoubleValue() + right->getDoubleValue());
}
else if (node->type == NODE_TYPE_OPERATOR_BINARY_MINUS) {
auto left = lhs->castToNumber(this);
auto right = rhs->castToNumber(this);
if (left->isNullValue() && ! lhs->isNullValue()) {
// conversion of lhs failed
return createNodeValueNull();
}
if (right->isNullValue() && ! rhs->isNullValue()) {
// conversion of rhs failed
return createNodeValueNull();
}
bool useDoublePrecision = (left->isDoubleValue() || right->isDoubleValue());
if (! useDoublePrecision) {
auto l = left->getIntValue();
auto r = right->getIntValue();
// check if the result would overflow
useDoublePrecision = IsUnsafeSubtraction<int64_t>(l, r);
if (! useDoublePrecision) {
// can calculate using integers
return createArithmeticResultNode(l - r);
}
}
// must use double precision
return createArithmeticResultNode(left->getDoubleValue() - right->getDoubleValue());
}
else if (node->type == NODE_TYPE_OPERATOR_BINARY_TIMES) {
auto left = lhs->castToNumber(this);
auto right = rhs->castToNumber(this);
if (left->isNullValue() && ! lhs->isNullValue()) {
// conversion of lhs failed
return createNodeValueNull();
}
if (right->isNullValue() && ! rhs->isNullValue()) {
// conversion of rhs failed
return createNodeValueNull();
}
bool useDoublePrecision = (left->isDoubleValue() || right->isDoubleValue());
if (! useDoublePrecision) {
auto l = left->getIntValue();
auto r = right->getIntValue();
// check if the result would overflow
useDoublePrecision = IsUnsafeMultiplication<int64_t>(l, r);
if (! useDoublePrecision) {
// can calculate using integers
return createArithmeticResultNode(l * r);
}
}
// must use double precision
return createArithmeticResultNode(left->getDoubleValue() * right->getDoubleValue());
}
else if (node->type == NODE_TYPE_OPERATOR_BINARY_DIV) {
auto left = lhs->castToNumber(this);
auto right = rhs->castToNumber(this);
if (left->isNullValue() && ! lhs->isNullValue()) {
// conversion of lhs failed
return createNodeValueNull();
}
if (right->isNullValue() && ! rhs->isNullValue()) {
// conversion of rhs failed
return createNodeValueNull();
}
bool useDoublePrecision = (left->isDoubleValue() || right->isDoubleValue());
if (! useDoublePrecision) {
auto l = left->getIntValue();
auto r = right->getIntValue();
if (r == 0) {
_query->registerWarning(TRI_ERROR_QUERY_DIVISION_BY_ZERO);
return createNodeValueNull();
}
// check if the result would overflow
useDoublePrecision = (IsUnsafeDivision<int64_t>(l, r) || r < -1 || r > 1);
if (! useDoublePrecision) {
// can calculate using integers
return createArithmeticResultNode(l / r);
}
}
if (right->getDoubleValue() == 0.0) {
_query->registerWarning(TRI_ERROR_QUERY_DIVISION_BY_ZERO);
return createNodeValueNull();
}
return createArithmeticResultNode(left->getDoubleValue() / right->getDoubleValue());
}
else if (node->type == NODE_TYPE_OPERATOR_BINARY_MOD) {
auto left = lhs->castToNumber(this);
auto right = rhs->castToNumber(this);
if (left->isNullValue() && ! lhs->isNullValue()) {
// conversion of lhs failed
return createNodeValueNull();
}
if (right->isNullValue() && ! rhs->isNullValue()) {
// conversion of rhs failed
return createNodeValueNull();
}
bool useDoublePrecision = (left->isDoubleValue() || right->isDoubleValue());
if (! useDoublePrecision) {
auto l = left->getIntValue();
auto r = right->getIntValue();
if (r == 0) {
_query->registerWarning(TRI_ERROR_QUERY_DIVISION_BY_ZERO);
return createNodeValueNull();
}
// check if the result would overflow
useDoublePrecision = IsUnsafeDivision<int64_t>(l, r);
if (! useDoublePrecision) {
// can calculate using integers
return createArithmeticResultNode(l % r);
}
}
if (right->getDoubleValue() == 0.0) {
_query->registerWarning(TRI_ERROR_QUERY_DIVISION_BY_ZERO);
return createNodeValueNull();
}
return createArithmeticResultNode(fmod(left->getDoubleValue(), right->getDoubleValue()));
}
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "invalid operator");
}
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the ternary operator
/// if the condition is constant, the operator will be replaced with either the
/// true part or the false part
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeTernaryOperator (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_OPERATOR_TERNARY);
TRI_ASSERT(node->numMembers() == 3);
AstNode* condition = node->getMember(0);
AstNode* truePart = node->getMember(1);
AstNode* falsePart = node->getMember(2);
if (condition == nullptr ||
truePart == nullptr ||
falsePart == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
if (! condition->isConstant()) {
return node;
}
if (condition->isTrue()) {
// condition is always true, replace ternary operation with true part
return truePart;
}
// condition is always false, replace ternary operation with false part
return falsePart;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes a call to a built-in function
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeFunctionCall (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_FCALL);
TRI_ASSERT(node->numMembers() == 1);
auto func = static_cast<Function*>(node->getData());
TRI_ASSERT(func != nullptr);
if (! func->isDeterministic) {
// non-deterministic function
return node;
}
if (! node->getMember(0)->isConstant()) {
// arguments to function call are not constant
return node;
}
return executeConstExpression(node);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes a reference to a variable
/// references are replaced with constants if possible
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeReference (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_REFERENCE);
auto variable = static_cast<Variable*>(node->getData());
if (variable == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
// constant propagation
if (variable->constValue() == nullptr) {
return node;
}
if (node->hasFlag(FLAG_KEEP_VARIABLENAME)) {
// this is a reference to a variable name, not a reference to the result
// this can be happen for variables that are specified in the COLLECT...KEEP clause
return node;
}
return static_cast<AstNode*>(variable->constValue());
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes indexed access, e.g. a[0] or a['foo']
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeIndexedAccess (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_INDEXED_ACCESS);
TRI_ASSERT(node->numMembers() == 2);
auto index = node->getMember(1);
if (index->isConstant() &&
index->type == NODE_TYPE_VALUE &&
index->value.type == VALUE_TYPE_STRING) {
// found a string value (e.g. a['foo']). now turn this into
// an attribute access (e.g. a.foo) in order to make the node qualify
// for being turned into an index range later
char const* indexValue = index->getStringValue();
if (indexValue != nullptr &&
(indexValue[0] < '0' || indexValue[0] > '9')) {
// we have to be careful with numeric values here...
// e.g. array['0'] is not the same as array.0 but must remain a['0'] or (a[0])
return createNodeAttributeAccess(node->getMember(0), indexValue, index->getStringLength());
}
}
// can't optimize when we get here
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the LET statement
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeLet (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_LET);
TRI_ASSERT(node->numMembers() >= 2);
AstNode* variable = node->getMember(0);
AstNode* expression = node->getMember(1);
bool const hasCondition = (node->numMembers() > 2);
auto v = static_cast<Variable*>(variable->getData());
TRI_ASSERT(v != nullptr);
if (! hasCondition && 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<void*>(expression));
}
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the FILTER statement
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeFilter (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_FILTER);
TRI_ASSERT(node->numMembers() == 1);
AstNode* expression = node->getMember(0);
if (expression == nullptr || ! expression->isDeterministic()) {
return node;
}
if (expression->isTrue()) {
// optimize away the filter if it is always true
return createNodeFilter(createNodeValueBool(true));
}
if (expression->isFalse()) {
// optimize away the filter if it is always false
return createNodeFilter(createNodeValueBool(false));
}
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimizes the FOR statement
/// no real optimizations are done here, but we do an early check if the
/// FOR loop operand is actually a list
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::optimizeFor (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_FOR);
TRI_ASSERT(node->numMembers() == 2);
AstNode* expression = node->getMember(1);
if (expression == nullptr) {
return node;
}
if (expression->isConstant() &&
expression->type != NODE_TYPE_ARRAY) {
// right-hand operand to FOR statement is no array
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_ARRAY_EXPECTED,
TRI_errno_string(TRI_ERROR_QUERY_ARRAY_EXPECTED) + std::string(" as operand to FOR loop"));
}
// no real optimizations will be done here
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST node from JSON
/// if copyStringValues is `true`, then string values will be copied and will
/// be freed with the query afterwards. when set to `false`, string values
/// will not be copied and not freed by the query. the caller needs to make
/// sure then that string values are valid through the query lifetime.
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::nodeFromJson (TRI_json_t const* json,
bool copyStringValues) {
TRI_ASSERT(json != nullptr);
if (json->_type == TRI_JSON_BOOLEAN) {
return createNodeValueBool(json->_value._boolean);
}
if (json->_type == TRI_JSON_NUMBER) {
return createNodeValueDouble(json->_value._number);
}
if (json->_type == TRI_JSON_STRING ||
json->_type == TRI_JSON_STRING_REFERENCE) {
size_t const length = json->_value._string.length - 1;
if (copyStringValues) {
// we must copy string values!
char const* value = _query->registerString(json->_value._string.data, length);
return createNodeValueString(value, length);
}
// we can get away without copying string values
return createNodeValueString(json->_value._string.data, length);
}
if (json->_type == TRI_JSON_ARRAY) {
auto node = createNodeArray();
size_t const n = TRI_LengthArrayJson(json);
for (size_t i = 0; i < n; ++i) {
node->addMember(nodeFromJson(static_cast<TRI_json_t const*>(TRI_AddressVector(&json->_value._objects, i)), copyStringValues));
}
return node;
}
if (json->_type == TRI_JSON_OBJECT) {
auto node = createNodeObject();
size_t const n = TRI_LengthVector(&json->_value._objects);
for (size_t i = 0; i < n; i += 2) {
auto key = static_cast<TRI_json_t const*>(TRI_AddressVector(&json->_value._objects, i));
auto value = static_cast<TRI_json_t const*>(TRI_AddressVector(&json->_value._objects, i + 1));
if (! TRI_IsStringJson(key) || value == nullptr) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unexpected type found in object node");
}
char const* attributeName = key->_value._string.data;
size_t const nameLength = key->_value._string.length - 1;
if (copyStringValues) {
// create a copy of the string value
attributeName = _query->registerString(key->_value._string.data, nameLength);
}
node->addMember(createNodeObjectElement(attributeName, nameLength, nodeFromJson(value, copyStringValues)));
}
return node;
}
return createNodeValueNull();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief traverse the AST, using pre- and post-order visitors
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::traverseAndModify (AstNode* node,
std::function<bool(AstNode const*, void*)> preVisitor,
std::function<AstNode*(AstNode*, void*)> visitor,
std::function<void(AstNode const*, void*)> postVisitor,
void* data) {
if (node == nullptr) {
return nullptr;
}
if (! preVisitor(node, data)) {
return node;
}
size_t const n = node->numMembers();
for (size_t i = 0; i < n; ++i) {
auto member = node->getMemberUnchecked(i);
if (member != nullptr) {
AstNode* result = traverseAndModify(member, preVisitor, visitor, postVisitor, data);
if (result != node) {
TRI_ASSERT_EXPENSIVE(node != nullptr);
node->changeMember(i, result);
}
}
}
auto result = visitor(node, data);
postVisitor(node, data);
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief traverse the AST, using a depth-first visitor
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::traverseAndModify (AstNode* node,
std::function<AstNode*(AstNode*, void*)> visitor,
void* data) {
if (node == nullptr) {
return nullptr;
}
size_t const n = node->numMembers();
for (size_t i = 0; i < n; ++i) {
auto member = node->getMemberUnchecked(i);
if (member != nullptr) {
AstNode* result = traverseAndModify(member, visitor, data);
if (result != node) {
node->changeMember(i, result);
}
}
}
return visitor(node, data);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief traverse the AST, using pre- and post-order visitors
////////////////////////////////////////////////////////////////////////////////
void Ast::traverseReadOnly (AstNode const* node,
std::function<void(AstNode const*, void*)> preVisitor,
std::function<void(AstNode const*, void*)> visitor,
std::function<void(AstNode const*, void*)> postVisitor,
void* data) {
if (node == nullptr) {
return;
}
preVisitor(node, data);
size_t const n = node->numMembers();
for (size_t i = 0; i < n; ++i) {
auto member = node->getMemberUnchecked(i);
if (member != nullptr) {
traverseReadOnly(member, preVisitor, visitor, postVisitor, data);
}
}
visitor(node, data);
postVisitor(node, data);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief traverse the AST using a visitor depth-first, with const nodes
////////////////////////////////////////////////////////////////////////////////
void Ast::traverseReadOnly (AstNode const* node,
std::function<void(AstNode const*, void*)> visitor,
void* data) {
if (node == nullptr) {
return;
}
size_t const n = node->numMembers();
for (size_t i = 0; i < n; ++i) {
auto member = node->getMemberUnchecked(i);
if (member != nullptr) {
traverseReadOnly(const_cast<AstNode const*>(member), visitor, data);
}
}
visitor(node, data);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief normalize a function name
////////////////////////////////////////////////////////////////////////////////
std::pair<std::string, bool> Ast::normalizeFunctionName (char const* name) {
TRI_ASSERT(name != nullptr);
char* upperName = TRI_UpperAsciiString(TRI_UNKNOWN_MEM_ZONE, name);
if (upperName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
std::string functionName(upperName);
TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, upperName);
if (functionName.find(':') == std::string::npos) {
// prepend default namespace for internal functions
return std::make_pair(functionName, true);
}
// user-defined function
return std::make_pair(functionName, false);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a node of the specified type
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNode (AstNodeType type) {
auto node = new AstNode(type);
try {
// register the node so it gets freed automatically later
_query->addNode(node);
}
catch (...) {
delete node;
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
return node;
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}"
// End: