//////////////////////////////////////////////////////////////////////////////// /// 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 Michael Hackstein //////////////////////////////////////////////////////////////////////////////// #include "BaseOptions.h" #include "Aql/Ast.h" #include "Aql/Expression.h" #include "Aql/Query.h" #include "Indexes/Index.h" #include "VocBase/TraverserCache.h" #include #include #include #include using namespace arangodb; using namespace arangodb::graph; using namespace arangodb::traverser; BaseOptions::LookupInfo::LookupInfo() : expression(nullptr), indexCondition(nullptr), conditionNeedUpdate(false), conditionMemberToUpdate(0) { // NOTE: We need exactly one in this case for the optimizer to update idxHandles.resize(1); }; BaseOptions::LookupInfo::~LookupInfo() { if (expression != nullptr) { delete expression; } } BaseOptions::LookupInfo::LookupInfo(arangodb::aql::Query* query, VPackSlice const& info, VPackSlice const& shards) { TRI_ASSERT(shards.isArray()); idxHandles.reserve(shards.length()); conditionNeedUpdate = arangodb::basics::VelocyPackHelper::getBooleanValue( info, "condNeedUpdate", false); conditionMemberToUpdate = arangodb::basics::VelocyPackHelper::getNumericValue( info, "condMemberToUpdate", 0); VPackSlice read = info.get("handle"); if (!read.isObject()) { THROW_ARANGO_EXCEPTION_MESSAGE( TRI_ERROR_BAD_PARAMETER, "Each lookup requires handle to be an object"); } read = read.get("id"); if (!read.isString()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, "Each handle requires id to be a string"); } std::string idxId = read.copyString(); auto trx = query->trx(); for (auto const& it : VPackArrayIterator(shards)) { if (!it.isString()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, "Shards have to be a list of strings"); } idxHandles.emplace_back(trx->getIndexByIdentifier(it.copyString(), idxId)); } read = info.get("expression"); if (!read.isObject()) { THROW_ARANGO_EXCEPTION_MESSAGE( TRI_ERROR_BAD_PARAMETER, "Each lookup requires expression to be an object"); } expression = new aql::Expression(query->ast(), read); read = info.get("condition"); if (!read.isObject()) { THROW_ARANGO_EXCEPTION_MESSAGE( TRI_ERROR_BAD_PARAMETER, "Each lookup requires condition to be an object"); } indexCondition = new aql::AstNode(query->ast(), read); } BaseOptions::LookupInfo::LookupInfo(LookupInfo const& other) : idxHandles(other.idxHandles), expression(nullptr), indexCondition(other.indexCondition), conditionNeedUpdate(other.conditionNeedUpdate), conditionMemberToUpdate(other.conditionMemberToUpdate) { expression = other.expression->clone(nullptr); } void BaseOptions::LookupInfo::buildEngineInfo(VPackBuilder& result) const { result.openObject(); result.add(VPackValue("handle")); // We only run toVelocyPack on Coordinator. TRI_ASSERT(idxHandles.size() == 1); result.openObject(); idxHandles[0].toVelocyPack(result, false); result.close(); result.add(VPackValue("expression")); result.openObject(); // We need to encapsulate the expression into an // expression object result.add(VPackValue("expression")); expression->toVelocyPack(result, true); result.close(); result.add(VPackValue("condition")); indexCondition->toVelocyPack(result, true); result.add("condNeedUpdate", VPackValue(conditionNeedUpdate)); result.add("condMemberToUpdate", VPackValue(conditionMemberToUpdate)); result.close(); } double BaseOptions::LookupInfo::estimateCost(size_t& nrItems) const { // If we do not have an index yet we cannot do anything. // Should NOT be the case TRI_ASSERT(!idxHandles.empty()); auto idx = idxHandles[0].getIndex(); if (idx->hasSelectivityEstimate()) { double expected = 1 / idx->selectivityEstimate(); nrItems += static_cast(expected); return expected; } // Some hard-coded value nrItems += 1000; return 1000.0; } BaseOptions::BaseOptions(transaction::Methods* trx) : _ctx(new aql::FixedVarExpressionContext()), _trx(trx), _tmpVar(nullptr), _isCoordinator(arangodb::ServerState::instance()->isCoordinator()), _cache(std::make_unique(_trx)) {} BaseOptions::BaseOptions(BaseOptions const& other) : _ctx(new aql::FixedVarExpressionContext()), _trx(other._trx), _tmpVar(nullptr), _isCoordinator(arangodb::ServerState::instance()->isCoordinator()), _cache(std::make_unique(_trx)) { TRI_ASSERT(other._baseLookupInfos.empty()); TRI_ASSERT(other._tmpVar == nullptr); } BaseOptions::BaseOptions(arangodb::aql::Query* query, VPackSlice info, VPackSlice collections) : _ctx(new aql::FixedVarExpressionContext()), _trx(query->trx()), _tmpVar(nullptr), _isCoordinator(arangodb::ServerState::instance()->isCoordinator()), _cache(std::make_unique(_trx)) { VPackSlice read = info.get("tmpVar"); if (!read.isObject()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, "The options require a tmpVar"); } _tmpVar = query->ast()->variables()->createVariable(read); read = info.get("baseLookupInfos"); if (!read.isArray()) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, "The options require a baseLookupInfos"); } size_t length = read.length(); TRI_ASSERT(read.length() == collections.length()); _baseLookupInfos.reserve(length); for (size_t j = 0; j < length; ++j) { _baseLookupInfos.emplace_back(query, read.at(j), collections.at(j)); } } BaseOptions::~BaseOptions() { delete _ctx; } void BaseOptions::toVelocyPackIndexes(VPackBuilder& builder) const { builder.openObject(); injectVelocyPackIndexes(builder); builder.close(); } void BaseOptions::buildEngineInfo(VPackBuilder& result) const { result.openObject(); injectEngineInfo(result); result.close(); } void BaseOptions::setVariable(aql::Variable const* variable) { _tmpVar = variable; } void BaseOptions::addLookupInfo(aql::Ast* ast, std::string const& collectionName, std::string const& attributeName, aql::AstNode* condition) { injectLookupInfoInList(_baseLookupInfos, ast, collectionName, attributeName, condition); } void BaseOptions::injectLookupInfoInList(std::vector& list, aql::Ast* ast, std::string const& collectionName, std::string const& attributeName, aql::AstNode* condition) { LookupInfo info; info.indexCondition = condition; info.expression = new aql::Expression(ast, condition->clone(ast)); bool res = _trx->getBestIndexHandleForFilterCondition( collectionName, info.indexCondition, _tmpVar, 1000, info.idxHandles[0]); TRI_ASSERT(res); // Right now we have an enforced edge index which will // always fit. if (!res) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "expected edge index not found"); } // We now have to check if we need _from / _to inside the index lookup and // which position // it is used in. Such that the traverser can update the respective string // value in-place std::pair> pathCmp; for (size_t i = 0; i < info.indexCondition->numMembers(); ++i) { // We search through the nary-and and look for EQ - _from/_to auto eq = info.indexCondition->getMemberUnchecked(i); if (eq->type != arangodb::aql::AstNodeType::NODE_TYPE_OPERATOR_BINARY_EQ) { // No equality. Skip continue; } TRI_ASSERT(eq->numMembers() == 2); // It is sufficient to only check member one. // We build the condition this way. auto mem = eq->getMemberUnchecked(0); if (mem->isAttributeAccessForVariable(pathCmp)) { if (pathCmp.first != _tmpVar) { continue; } if (pathCmp.second.size() == 1 && pathCmp.second[0].name == attributeName) { info.conditionNeedUpdate = true; info.conditionMemberToUpdate = i; break; } continue; } } list.emplace_back(std::move(info)); } void BaseOptions::clearVariableValues() { _ctx->clearVariableValues(); } void BaseOptions::setVariableValue(aql::Variable const* var, aql::AqlValue const value) { _ctx->setVariableValue(var, value); } void BaseOptions::serializeVariables(VPackBuilder& builder) const { TRI_ASSERT(builder.isOpenArray()); _ctx->serializeAllVariables(_trx, builder); } arangodb::transaction::Methods* BaseOptions::trx() const { return _trx; } arangodb::traverser::TraverserCache* BaseOptions::cache() const { return _cache.get(); } void BaseOptions::injectVelocyPackIndexes(VPackBuilder& builder) const { TRI_ASSERT(builder.isOpenObject()); // base indexes builder.add("base", VPackValue(VPackValueType::Array)); for (auto const& it : _baseLookupInfos) { for (auto const& it2 : it.idxHandles) { builder.openObject(); it2.getIndex()->toVelocyPack(builder, false); builder.close(); } } builder.close(); } void BaseOptions::injectEngineInfo(VPackBuilder& result) const { TRI_ASSERT(result.isOpenObject()); result.add(VPackValue("baseLookupInfos")); result.openArray(); for (auto const& it : _baseLookupInfos) { it.buildEngineInfo(result); } result.close(); result.add(VPackValue("tmpVar")); _tmpVar->toVelocyPack(result); } arangodb::aql::Expression* BaseOptions::getEdgeExpression( size_t cursorId) const { TRI_ASSERT(!_baseLookupInfos.empty()); TRI_ASSERT(_baseLookupInfos.size() > cursorId); return _baseLookupInfos[cursorId].expression; } bool BaseOptions::evaluateExpression(arangodb::aql::Expression* expression, VPackSlice value) const { if (expression == nullptr) { return true; } TRI_ASSERT(!expression->isV8()); expression->setVariable(_tmpVar, value); bool mustDestroy = false; aql::AqlValue res = expression->execute(_trx, _ctx, mustDestroy); TRI_ASSERT(res.isBoolean()); bool result = res.toBoolean(); expression->clearVariable(_tmpVar); if (mustDestroy) { res.destroy(); } return result; } double BaseOptions::costForLookupInfoList( std::vector const& list, size_t& createItems) const { double cost = 0; createItems = 0; for (auto const& li : list) { cost += li.estimateCost(createItems); } return cost; }