//////////////////////////////////////////////////////////////////////////////// /// 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 "Expression.h" #include "Aql/AqlItemBlock.h" #include "Aql/AqlValue.h" #include "Aql/Ast.h" #include "Aql/AttributeAccessor.h" #include "Aql/Executor.h" #include "Aql/Quantifier.h" #include "Aql/Query.h" #include "Aql/V8Expression.h" #include "Aql/Variable.h" #include "Basics/Exceptions.h" #include "Basics/JsonHelper.h" #include "Basics/StringBuffer.h" #include "Basics/VelocyPackHelper.h" #include "Basics/json.h" #include #include #include #include using namespace arangodb::aql; using Json = arangodb::basics::Json; using JsonHelper = arangodb::basics::JsonHelper; using VelocyPackHelper = arangodb::basics::VelocyPackHelper; //////////////////////////////////////////////////////////////////////////////// /// @brief register warning //////////////////////////////////////////////////////////////////////////////// static void RegisterWarning(arangodb::aql::Ast const* ast, char const* functionName, int code) { std::string msg; if (code == TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH) { msg = arangodb::basics::Exception::FillExceptionString(code, functionName); } else { msg.append("in function '"); msg.append(functionName); msg.append("()': "); msg.append(TRI_errno_string(code)); } ast->query()->registerWarning(code, msg.c_str()); } //////////////////////////////////////////////////////////////////////////////// /// @brief create the expression //////////////////////////////////////////////////////////////////////////////// Expression::Expression(Ast* ast, AstNode* node) : _ast(ast), _executor(_ast->query()->executor()), _node(node), _type(UNPROCESSED), _canThrow(true), _canRunOnDBServer(false), _isDeterministic(false), _hasDeterminedAttributes(false), _built(false), _attributes(), _buffer(TRI_UNKNOWN_MEM_ZONE) { TRI_ASSERT(_ast != nullptr); TRI_ASSERT(_executor != nullptr); TRI_ASSERT(_node != nullptr); } //////////////////////////////////////////////////////////////////////////////// /// @brief create an expression from JSON //////////////////////////////////////////////////////////////////////////////// Expression::Expression(Ast* ast, arangodb::basics::Json const& json) : Expression(ast, new AstNode(ast, json.get("expression"))) {} //////////////////////////////////////////////////////////////////////////////// /// @brief destroy the expression //////////////////////////////////////////////////////////////////////////////// Expression::~Expression() { if (_built) { switch (_type) { case JSON: TRI_ASSERT(_data != nullptr); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, _data); break; case ATTRIBUTE: { TRI_ASSERT(_accessor != nullptr); delete _accessor; break; } case V8: delete _func; break; case SIMPLE: case UNPROCESSED: { // nothing to do break; } } } } //////////////////////////////////////////////////////////////////////////////// /// @brief return all variables used in the expression //////////////////////////////////////////////////////////////////////////////// void Expression::variables(std::unordered_set& result) const { Ast::getReferencedVariables(_node, result); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute the expression //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::execute(arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { if (!_built) { buildExpression(); } TRI_ASSERT(_type != UNPROCESSED); TRI_ASSERT(_built); // and execute switch (_type) { case JSON: { // TODO TRI_ASSERT(_data != nullptr); VPackBuilder builder; JsonHelper::toVelocyPack(_data, builder); return AqlValue$(builder); } case SIMPLE: { return executeSimpleExpression(_node, trx, argv, startPos, vars, regs, true); } case ATTRIBUTE: { TRI_ASSERT(_accessor != nullptr); return _accessor->get(trx, argv, startPos, vars, regs); } case V8: { TRI_ASSERT(_func != nullptr); try { ISOLATE; return _func->execute(isolate, _ast->query(), trx, argv, startPos, vars, regs); } catch (arangodb::basics::Exception& ex) { if (_ast->query()->verboseErrors()) { ex.addToMessage(" while evaluating expression "); auto json = _node->toJson(TRI_UNKNOWN_MEM_ZONE, false); if (json != nullptr) { ex.addToMessage(arangodb::basics::JsonHelper::toString(json)); TRI_Free(TRI_UNKNOWN_MEM_ZONE, json); } } throw; } } case UNPROCESSED: { // fall-through to exception } } THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "invalid simple expression"); } //////////////////////////////////////////////////////////////////////////////// /// @brief replace variables in the expression with other variables //////////////////////////////////////////////////////////////////////////////// void Expression::replaceVariables( std::unordered_map const& replacements) { _node = _ast->clone(_node); TRI_ASSERT(_node != nullptr); _node = _ast->replaceVariables(const_cast(_node), replacements); invalidate(); } //////////////////////////////////////////////////////////////////////////////// /// @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` //////////////////////////////////////////////////////////////////////////////// void Expression::replaceVariableReference(Variable const* variable, AstNode const* node) { _node = _ast->clone(_node); TRI_ASSERT(_node != nullptr); _node = _ast->replaceVariableReference(const_cast(_node), variable, node); invalidate(); if (_type == ATTRIBUTE) { if (_built) { delete _accessor; _accessor = nullptr; _built = false; } // must even set back the expression type so the expression will be analyzed // again _type = UNPROCESSED; } const_cast(_node)->clearFlags(); _attributes.clear(); _hasDeterminedAttributes = false; } //////////////////////////////////////////////////////////////////////////////// /// @brief invalidates an expression /// this only has an effect for V8-based functions, which need to be created, /// used and destroyed in the same context. when a V8 function is used across /// multiple V8 contexts, it must be invalidated in between //////////////////////////////////////////////////////////////////////////////// void Expression::invalidate() { if (_type == V8) { // V8 expressions need a special handling if (_built) { delete _func; _func = nullptr; _built = false; } } // we do not need to invalidate the other expression type // expression data will be freed in the destructor } //////////////////////////////////////////////////////////////////////////////// /// @brief find a value in an AQL list node /// this performs either a binary search (if the node is sorted) or a /// linear search (if the node is not sorted) //////////////////////////////////////////////////////////////////////////////// bool Expression::findInArray(AqlValue$ const& left, AqlValue$ const& right, arangodb::AqlTransaction* trx, AstNode const* node) const { TRI_ASSERT(right.isArray()); size_t const n = right.length(); if (n > 3 && (node->getMember(1)->isSorted() || ((node->type == NODE_TYPE_OPERATOR_BINARY_IN || node->type == NODE_TYPE_OPERATOR_BINARY_NIN) && node->getBoolValue()))) { // node values are sorted. can use binary search size_t l = 0; size_t r = n - 1; while (true) { // determine midpoint size_t m = l + ((r - l) / 2); int compareResult = AqlValue$::Compare(trx, left, AqlValue$(right.at(m).slice()), true); if (compareResult == 0) { // item found in the list return true; } if (compareResult < 0) { if (m == 0) { // not found return false; } r = m - 1; } else { l = m + 1; } if (r < l) { return false; } } } // use linear search for (size_t i = 0; i < n; ++i) { int compareResult = AqlValue$::Compare(trx, left, AqlValue$(right.at(i).slice()), false); if (compareResult == 0) { // item found in the list return true; } } return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief analyze the expression (determine its type etc.) //////////////////////////////////////////////////////////////////////////////// void Expression::analyzeExpression() { TRI_ASSERT(_type == UNPROCESSED); TRI_ASSERT(_built == false); if (_node->isConstant()) { // expression is a constant value _type = JSON; _canThrow = false; _canRunOnDBServer = true; _isDeterministic = true; _data = nullptr; } else if (_node->isSimple()) { // expression is a simple expression _type = SIMPLE; _canThrow = _node->canThrow(); _canRunOnDBServer = _node->canRunOnDBServer(); _isDeterministic = _node->isDeterministic(); if (_node->type == NODE_TYPE_ATTRIBUTE_ACCESS) { TRI_ASSERT(_node->numMembers() == 1); auto member = _node->getMemberUnchecked(0); std::vector parts{ static_cast(_node->getData())}; while (member->type == NODE_TYPE_ATTRIBUTE_ACCESS) { parts.insert(parts.begin(), static_cast(member->getData())); member = member->getMemberUnchecked(0); } if (member->type == NODE_TYPE_REFERENCE) { auto v = static_cast(member->getData()); // specialize the simple expression into an attribute accessor _accessor = new AttributeAccessor(parts, v); _type = ATTRIBUTE; _built = true; } } } else { // expression is a V8 expression _type = V8; _canThrow = _node->canThrow(); _canRunOnDBServer = _node->canRunOnDBServer(); _isDeterministic = _node->isDeterministic(); _func = nullptr; if (!_hasDeterminedAttributes) { // determine all top-level attributes used in expression only once // as this might be expensive _hasDeterminedAttributes = true; bool isSafeForOptimization; _attributes = Ast::getReferencedAttributes(_node, isSafeForOptimization); if (!isSafeForOptimization) { _attributes.clear(); // unfortunately there are not only top-level attribute accesses but // also other accesses, e.g. the index values or accesses of the whole // value. // for example, we cannot optimize LET x = a +1 or LET x = a[0], but LET // x = a._key } } } } //////////////////////////////////////////////////////////////////////////////// /// @brief build the expression //////////////////////////////////////////////////////////////////////////////// void Expression::buildExpression() { TRI_ASSERT(!_built); if (_type == UNPROCESSED) { analyzeExpression(); } if (_type == JSON) { TRI_ASSERT(_data == nullptr); // generate a constant value _data = _node->toJsonValue(TRI_UNKNOWN_MEM_ZONE); if (_data == nullptr) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "invalid json in simple expression"); } } else if (_type == V8) { // generate a V8 expression _func = _executor->generateExpression(_node); // optimizations for the generated function if (_func != nullptr && !_attributes.empty()) { // pass which variables do not need to be fully constructed _func->setAttributeRestrictions(_attributes); } } _built = true; } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE, the convention is that /// the resulting AqlValue will be destroyed outside eventually //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpression( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs, bool doCopy) { switch (node->type) { case NODE_TYPE_ATTRIBUTE_ACCESS: return executeSimpleExpressionAttributeAccess(node, trx, argv, startPos, vars, regs); case NODE_TYPE_INDEXED_ACCESS: return executeSimpleExpressionIndexedAccess(node, trx, argv, startPos, vars, regs); case NODE_TYPE_ARRAY: return executeSimpleExpressionArray(node, trx, argv, startPos, vars, regs); case NODE_TYPE_OBJECT: return executeSimpleExpressionObject(node, trx, argv, startPos, vars, regs); case NODE_TYPE_VALUE: return executeSimpleExpressionValue(node); case NODE_TYPE_REFERENCE: return executeSimpleExpressionReference(node, trx, argv, startPos, vars, regs, doCopy); case NODE_TYPE_FCALL: return executeSimpleExpressionFCall(node, trx, argv, startPos, vars, regs); case NODE_TYPE_RANGE: return executeSimpleExpressionRange(node, trx, argv, startPos, vars, regs); case NODE_TYPE_OPERATOR_UNARY_NOT: return executeSimpleExpressionNot(node, trx, argv, startPos, vars, regs); case NODE_TYPE_OPERATOR_BINARY_AND: case NODE_TYPE_OPERATOR_BINARY_OR: return executeSimpleExpressionAndOr(node, trx, argv, startPos, vars, regs); 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: return executeSimpleExpressionComparison(node, trx, argv, startPos, vars, regs); 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: return executeSimpleExpressionArrayComparison(node, trx, argv, startPos, vars, regs); case NODE_TYPE_OPERATOR_TERNARY: return executeSimpleExpressionTernary(node, trx, argv, startPos, vars, regs); case NODE_TYPE_EXPANSION: return executeSimpleExpressionExpansion(node, trx, argv, startPos, vars, regs); case NODE_TYPE_ITERATOR: return executeSimpleExpressionIterator(node, trx, argv, startPos, vars, regs); 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: return executeSimpleExpressionArithmetic(node, trx, argv, startPos, vars, regs); default: std::string msg("unhandled type '"); msg.append(node->getTypeString()); msg.append("' in executeSimpleExpression()"); THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, msg.c_str()); } } //////////////////////////////////////////////////////////////////////////////// /// @brief check whether this is an attribute access of any degree (e.g. a.b, /// a.b.c, ...) //////////////////////////////////////////////////////////////////////////////// bool Expression::isAttributeAccess() const { return _node->isAttributeAccessForVariable(); } //////////////////////////////////////////////////////////////////////////////// /// @brief check whether this is a reference access //////////////////////////////////////////////////////////////////////////////// bool Expression::isReference() const { return (_node->type == arangodb::aql::NODE_TYPE_REFERENCE); } //////////////////////////////////////////////////////////////////////////////// /// @brief check whether this is a constant node //////////////////////////////////////////////////////////////////////////////// bool Expression::isConstant() const { return _node->isConstant(); } //////////////////////////////////////////////////////////////////////////////// /// @brief stringify an expression /// note that currently stringification is only supported for certain node types //////////////////////////////////////////////////////////////////////////////// void Expression::stringify(arangodb::basics::StringBuffer* buffer) const { _node->stringify(buffer, true, false); } //////////////////////////////////////////////////////////////////////////////// /// @brief stringify an expression /// note that currently stringification is only supported for certain node types //////////////////////////////////////////////////////////////////////////////// void Expression::stringifyIfNotTooLong( arangodb::basics::StringBuffer* buffer) const { _node->stringify(buffer, true, true); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with ATTRIBUTE ACCESS //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionAttributeAccess( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { // object lookup, e.g. users.name TRI_ASSERT(node->numMembers() == 1); auto member = node->getMemberUnchecked(0); auto name = static_cast(node->getData()); AqlValue$ result = executeSimpleExpression(member, trx, argv, startPos, vars, regs, false); return result.get(name); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with INDEXED ACCESS //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionIndexedAccess( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { // array lookup, e.g. users[0] // note: it depends on the type of the value whether an array lookup or an // object lookup is performed // for example, if the value is an object, then its elements might be accessed // like this: // users['name'] or even users['0'] (as '0' is a valid attribute name, too) // if the value is an array, then string indexes might also be used and will // be converted to integers, e.g. // users['0'] is the same as users[0], users['-2'] is the same as users[-2] // etc. TRI_ASSERT(node->numMembers() == 2); auto member = node->getMember(0); auto index = node->getMember(1); AqlValue$ result = executeSimpleExpression(member, trx, argv, startPos, vars, regs, false); if (result.isArray()) { AqlValue$ indexResult = executeSimpleExpression( index, trx, argv, startPos, vars, regs, false); if (indexResult.isNumber()) { return result.at(indexResult.toInt64()); } if (indexResult.slice().isString()) { std::string const value = indexResult.slice().copyString(); try { // stoll() might throw an exception if the string is not a number int64_t position = static_cast(std::stoll(value)); return result.at(position); } catch (...) { // no number found. } } // fall-through to returning null } else if (result.isObject()) { AqlValue$ indexResult = executeSimpleExpression( index, trx, argv, startPos, vars, regs, false); if (indexResult.isNumber()) { std::string const indexString = std::to_string(indexResult.toInt64()); return result.get(indexString); } if (indexResult.slice().isString()) { std::string const indexString = indexResult.slice().copyString(); return result.get(indexString); } // fall-through to returning null } return AqlValue$(VelocyPackHelper::NullValue()); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with ARRAY //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionArray( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { if (node->isConstant()) { auto json = node->computeJson(); if (json == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } VPackBuilder builder; JsonHelper::toVelocyPack(json, builder); return AqlValue$(builder); } size_t const n = node->numMembers(); VPackBuilder builder; builder.openArray(); for (size_t i = 0; i < n; ++i) { auto member = node->getMemberUnchecked(i); AqlValue$ result = executeSimpleExpression(member, trx, argv, startPos, vars, regs, false); builder.add(result.slice()); } builder.close(); return AqlValue$(builder); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with OBJECT //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionObject( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { if (node->isConstant()) { auto json = node->computeJson(); if (json == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } VPackBuilder builder; JsonHelper::toVelocyPack(json, builder); return AqlValue$(builder); } VPackBuilder builder; builder.openObject(); size_t const n = node->numMembers(); for (size_t i = 0; i < n; ++i) { auto member = node->getMemberUnchecked(i); TRI_ASSERT(member->type == NODE_TYPE_OBJECT_ELEMENT); char const* key = member->getStringValue(); member = member->getMember(0); AqlValue$ result = executeSimpleExpression(member, trx, argv, startPos, vars, regs, false); builder.add(key, result.slice()); } builder.close(); return AqlValue$(builder); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with VALUE //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionValue(AstNode const* node) { auto json = node->computeJson(); if (json == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } VPackBuilder builder; JsonHelper::toVelocyPack(json, builder); return AqlValue$(builder); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with REFERENCE //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionReference( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs, bool doCopy) { auto v = static_cast(node->getData()); { auto it = _variables.find(v); if (it != _variables.end()) { return AqlValue$((*it).second); } } size_t i = 0; for (auto it = vars.begin(); it != vars.end(); ++it, ++i) { if ((*it)->name == v->name) { return AqlValue$(argv->getValueReference(startPos, regs[i]), trx, nullptr); } } std::string msg("variable not found '"); msg.append(v->name); msg.append("' in executeSimpleExpression()"); THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, msg.c_str()); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with RANGE //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionRange( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { auto low = node->getMember(0); auto high = node->getMember(1); AqlValue$ resultLow = executeSimpleExpression(low, trx, argv, startPos, vars, regs, false); AqlValue$ resultHigh = executeSimpleExpression( high, trx, argv, startPos, vars, regs, false); return AqlValue$(resultLow.toInt64(), resultHigh.toInt64()); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with FCALL //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionFCall( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { // some functions have C++ handlers // check if the called function has one auto func = static_cast(node->getData()); TRI_ASSERT(func->implementation != nullptr); auto member = node->getMemberUnchecked(0); TRI_ASSERT(member->type == NODE_TYPE_ARRAY); VPackBuilder builder; size_t const n = member->numMembers(); VPackFunctionParameters parameters; parameters.reserve(n); for (size_t i = 0; i < n; ++i) { auto arg = member->getMemberUnchecked(i); if (arg->type == NODE_TYPE_COLLECTION) { builder.clear(); builder.add(VPackValue( std::string(arg->getStringValue(), arg->getStringLength()))); parameters.emplace_back(AqlValue$(builder)); } else { parameters.emplace_back(executeSimpleExpression(arg, trx, argv, startPos, vars, regs, false)); } } return func->implementation(_ast->query(), trx, parameters); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with NOT //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionNot( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { AqlValue$ operand = executeSimpleExpression(node->getMember(0), trx, argv, startPos, vars, regs, false); return AqlValue$(operand.isFalse()); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with AND or OR //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionAndOr( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { AqlValue$ left = executeSimpleExpression(node->getMember(0), trx, argv, startPos, vars, regs, true); AqlValue$ right = executeSimpleExpression(node->getMember(1), trx, argv, startPos, vars, regs, true); if (node->type == NODE_TYPE_OPERATOR_BINARY_AND) { // AND if (left.isTrue()) { // left is true => return right return right; } // left is false, return left return left; } // OR if (left.isTrue()) { // left is true => return left return left; } // left is false => return right return right; } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with COMPARISON //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionComparison( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { AqlValue$ left = executeSimpleExpression(node->getMember(0), trx, argv, startPos, vars, regs, false); AqlValue$ right = executeSimpleExpression(node->getMember(1), trx, argv, startPos, vars, regs, false); if (node->type == NODE_TYPE_OPERATOR_BINARY_IN || node->type == NODE_TYPE_OPERATOR_BINARY_NIN) { // IN and NOT IN if (!right.isArray()) { // right operand must be a list, otherwise we return false // do not throw, but return "false" instead return AqlValue$(false); } bool result = findInArray(left, right, trx, node); if (node->type == NODE_TYPE_OPERATOR_BINARY_NIN) { // revert the result in case of a NOT IN result = !result; } return AqlValue$(result); } // all other comparison operators... // for equality and non-equality we can use a binary comparison bool compareUtf8 = (node->type != NODE_TYPE_OPERATOR_BINARY_EQ && node->type != NODE_TYPE_OPERATOR_BINARY_NE); int compareResult = AqlValue$::Compare(trx, left, right, compareUtf8); switch (node->type) { case NODE_TYPE_OPERATOR_BINARY_EQ: return AqlValue$(compareResult == 0); case NODE_TYPE_OPERATOR_BINARY_NE: return AqlValue$(compareResult != 0); case NODE_TYPE_OPERATOR_BINARY_LT: return AqlValue$(compareResult < 0); case NODE_TYPE_OPERATOR_BINARY_LE: return AqlValue$(compareResult <= 0); case NODE_TYPE_OPERATOR_BINARY_GT: return AqlValue$(compareResult > 0); case NODE_TYPE_OPERATOR_BINARY_GE: return AqlValue$(compareResult >= 0); default: std::string msg("unhandled type '"); msg.append(node->getTypeString()); msg.append("' in executeSimpleExpression()"); THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, msg.c_str()); } } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with ARRAY COMPARISON //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionArrayComparison( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { AqlValue$ left = executeSimpleExpression(node->getMember(0), trx, argv, startPos, vars, regs, false); AqlValue$ right = executeSimpleExpression(node->getMember(1), trx, argv, startPos, vars, regs, false); if (!left.isArray()) { // left operand must be an array // do not throw, but return "false" instead return AqlValue$(false); } if (node->type == NODE_TYPE_OPERATOR_BINARY_ARRAY_IN || node->type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN) { // IN and NOT IN if (!right.isArray()) { // right operand must be a list, otherwise we return false // do not throw, but return "false" instead return AqlValue$(false); } } size_t const n = left.length(); std::pair requiredMatches = Quantifier::RequiredMatches(n, node->getMember(2)); TRI_ASSERT(requiredMatches.first <= requiredMatches.second); // for equality and non-equality we can use a binary comparison bool const compareUtf8 = (node->type != NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ && node->type != NODE_TYPE_OPERATOR_BINARY_ARRAY_NE); bool overallResult = true; size_t matches = 0; size_t numLeft = n; for (size_t i = 0; i < n; ++i) { AqlValue$ leftItemValue(left.at(i)); bool result; // IN and NOT IN if (node->type == NODE_TYPE_OPERATOR_BINARY_ARRAY_IN || node->type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN) { result = findInArray(leftItemValue, right, trx, node); if (node->type == NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN) { // revert the result in case of a NOT IN result = !result; } } else { // other operators int compareResult = AqlValue$::Compare(trx, leftItemValue, right, compareUtf8); result = false; switch (node->type) { case NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ: result = (compareResult == 0); break; case NODE_TYPE_OPERATOR_BINARY_ARRAY_NE: result = (compareResult != 0); break; case NODE_TYPE_OPERATOR_BINARY_ARRAY_LT: result = (compareResult < 0); break; case NODE_TYPE_OPERATOR_BINARY_ARRAY_LE: result = (compareResult <= 0); break; case NODE_TYPE_OPERATOR_BINARY_ARRAY_GT: result = (compareResult > 0); break; case NODE_TYPE_OPERATOR_BINARY_ARRAY_GE: result = (compareResult >= 0); break; default: TRI_ASSERT(false); } } --numLeft; if (result) { ++matches; if (matches > requiredMatches.second) { // too many matches overallResult = false; break; } if (matches >= requiredMatches.first && matches + numLeft <= requiredMatches.second) { // enough matches overallResult = true; break; } } else { if (matches + numLeft < requiredMatches.first) { // too few matches overallResult = false; break; } } } return AqlValue$(overallResult); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with TERNARY //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionTernary( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { AqlValue$ condition = executeSimpleExpression(node->getMember(0), trx, argv, startPos, vars, regs, false); size_t position; if (condition.isTrue()) { // return true part position = 1; } else { // return false part position = 2; } return executeSimpleExpression(node->getMember(position), trx, argv, startPos, vars, regs, true); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with EXPANSION //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionExpansion( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { TRI_ASSERT(node->numMembers() == 5); // LIMIT int64_t offset = 0; int64_t count = INT64_MAX; auto limitNode = node->getMember(3); if (limitNode->type != NODE_TYPE_NOP) { AqlValue$ sub = executeSimpleExpression(limitNode->getMember(0), trx, argv, startPos, vars, regs, false); offset = sub.toInt64(); sub = executeSimpleExpression(limitNode->getMember(1), trx, argv, startPos, vars, regs, false); count = sub.toInt64(); } if (offset < 0 || count <= 0) { // no items to return... can already stop here return AqlValueReference(VelocyPackHelper::ArrayValue()); } // FILTER AstNode const* filterNode = node->getMember(2); if (filterNode->type == NODE_TYPE_NOP) { filterNode = nullptr; } else if (filterNode->isConstant()) { if (filterNode->isTrue()) { // filter expression is always true filterNode = nullptr; } else { // filter expression is always false return AqlValueReference(VelocyPackHelper::ArrayValue()); } } auto iterator = node->getMember(0); auto variable = static_cast(iterator->getMember(0)->getData()); auto levels = node->getIntValue(true); AqlValue$ value; if (levels > 1) { // flatten value... value = executeSimpleExpression(node->getMember(0), trx, argv, startPos, vars, regs, false); if (!value.isArray()) { return AqlValueReference(VelocyPackHelper::ArrayValue()); } VPackBuilder builder; builder.openArray(); // generate a new temporary for the flattened array std::function flatten = [&](AqlValue$ const& v, int64_t level) { if (!v.isArray()) { return; } size_t const n = v.length(); for (size_t i = 0; i < n; ++i) { AqlValue$ item(v.at(i)); bool const isArray = item.isArray(); if (!isArray || level == levels) { builder.add(item.slice()); } else if (isArray && level < levels) { flatten(item, level + 1); } } }; flatten(value, 1); builder.close(); value = AqlValue$(builder); } else { value = executeSimpleExpression(node->getMember(0), trx, argv, startPos, vars, regs, false); if (!value.isArray()) { return AqlValueReference(VelocyPackHelper::ArrayValue()); } } // RETURN // the default is to return array member unmodified AstNode const* projectionNode = node->getMember(1); if (node->getMember(4)->type != NODE_TYPE_NOP) { // return projection projectionNode = node->getMember(4); } VPackBuilder builder; builder.openArray(); size_t const n = value.length(); for (size_t i = 0; i < n; ++i) { AqlValue$ item(value.at(i)); setVariable(variable, item.slice()); bool takeItem = true; if (filterNode != nullptr) { // have a filter AqlValue$ sub = executeSimpleExpression(filterNode, trx, argv, startPos, vars, regs, false); takeItem = sub.isTrue(); } if (takeItem && offset > 0) { // there is an offset in place --offset; takeItem = false; } if (takeItem) { AqlValue$ sub = executeSimpleExpression(projectionNode, trx, argv, startPos, vars, regs, true); builder.add(sub.slice()); } clearVariable(variable); if (takeItem && count > 0) { // number of items to pick was restricted if (--count == 0) { // done break; } } } builder.close(); return AqlValue$(builder); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with ITERATOR //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionIterator( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->numMembers() == 2); return executeSimpleExpression(node->getMember(1), trx, argv, startPos, vars, regs, true); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute an expression of type SIMPLE with BINARY_* (+, -, * , /, %) //////////////////////////////////////////////////////////////////////////////// AqlValue$ Expression::executeSimpleExpressionArithmetic( AstNode const* node, arangodb::AqlTransaction* trx, AqlItemBlock const* argv, size_t startPos, std::vector const& vars, std::vector const& regs) { AqlValue$ lhs = executeSimpleExpression(node->getMember(0), trx, argv, startPos, vars, regs, true); if (lhs.isObject()) { return AqlValue$(VelocyPackHelper::NullValue(), AqlValue$::AqlValueType::REFERENCE_STICKY); } AqlValue$ rhs = executeSimpleExpression(node->getMember(1), trx, argv, startPos, vars, regs, true); if (rhs.isObject()) { return AqlValue$(VelocyPackHelper::NullValue(), AqlValue$::AqlValueType::REFERENCE_STICKY); } double const l = lhs.toDouble(); double const r = rhs.toDouble(); VPackBuilder builder; switch (node->type) { case NODE_TYPE_OPERATOR_BINARY_PLUS: builder.add(VPackValue(l + r)); return AqlValue$(builder); case NODE_TYPE_OPERATOR_BINARY_MINUS: builder.add(VPackValue(l - r)); return AqlValue$(builder); case NODE_TYPE_OPERATOR_BINARY_TIMES: builder.add(VPackValue(l * r)); return AqlValue$(builder); case NODE_TYPE_OPERATOR_BINARY_DIV: if (r == 0.0) { RegisterWarning(_ast, "/", TRI_ERROR_QUERY_DIVISION_BY_ZERO); return AqlValue$(VelocyPackHelper::NullValue(), AqlValue$::AqlValueType::REFERENCE_STICKY); } return AqlValue$(builder); case NODE_TYPE_OPERATOR_BINARY_MOD: if (r == 0.0) { RegisterWarning(_ast, "/", TRI_ERROR_QUERY_DIVISION_BY_ZERO); return AqlValue$(VelocyPackHelper::NullValue(), AqlValue$::AqlValueType::REFERENCE_STICKY); } builder.add(VPackValue(fmod(l, r))); return AqlValue$(builder); default: return AqlValue$(VelocyPackHelper::NullValue(), AqlValue$::AqlValueType::REFERENCE_STICKY); } }