//////////////////////////////////////////////////////////////////////////////// /// 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 Max Neunhoeffer //////////////////////////////////////////////////////////////////////////////// #include "ClusterNodes.h" #include "Aql/AqlValue.h" #include "Aql/Ast.h" #include "Aql/ClusterBlocks.h" #include "Aql/Collection.h" #include "Aql/ExecutionPlan.h" #include "Aql/GraphNode.h" #include "Aql/IndexNode.h" #include "Aql/ModificationNodes.h" #include "Aql/Query.h" #include "Transaction/Methods.h" #include using namespace arangodb::basics; using namespace arangodb::aql; namespace { arangodb::velocypack::StringRef const SortModeUnset("unset"); arangodb::velocypack::StringRef const SortModeMinElement("minelement"); arangodb::velocypack::StringRef const SortModeHeap("heap"); bool toSortMode( arangodb::velocypack::StringRef const& str, GatherNode::SortMode& mode ) noexcept { // std::map ~25-30% faster than std::unordered_map for small number of elements static std::map const NameToValue { { SortModeMinElement, GatherNode::SortMode::MinElement}, { SortModeHeap, GatherNode::SortMode::Heap} }; auto const it = NameToValue.find(str); if (it == NameToValue.end()) { TRI_ASSERT(false); return false; } mode = it->second; return true; } arangodb::velocypack::StringRef toString(GatherNode::SortMode mode) noexcept { switch (mode) { case GatherNode::SortMode::MinElement: return SortModeMinElement; case GatherNode::SortMode::Heap: return SortModeHeap; default: TRI_ASSERT(false); return {}; } } } /// @brief constructor for RemoteNode RemoteNode::RemoteNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : ExecutionNode(plan, base), _vocbase(&(plan->getAst()->query()->vocbase())), _server(base.get("server").copyString()), _ownName(base.get("ownName").copyString()), _queryId(base.get("queryId").copyString()), _isResponsibleForInitializeCursor(base.get("isResponsibleForInitializeCursor").getBoolean()) {} /// @brief creates corresponding ExecutionBlock std::unique_ptr RemoteNode::createBlock( ExecutionEngine& engine, std::unordered_map const& ) const { return std::make_unique( &engine, this, server(), ownName(), queryId() ); } /// @brief toVelocyPack, for RemoteNode void RemoteNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { // call base class method ExecutionNode::toVelocyPackHelperGeneric(nodes, flags); nodes.add("database", VPackValue(_vocbase->name())); nodes.add("server", VPackValue(_server)); nodes.add("ownName", VPackValue(_ownName)); nodes.add("queryId", VPackValue(_queryId)); nodes.add("isResponsibleForInitializeCursor", VPackValue(_isResponsibleForInitializeCursor)); // And close it: nodes.close(); } /// @brief estimateCost CostEstimate RemoteNode::estimateCost() const { if (_dependencies.size() == 1) { CostEstimate estimate = _dependencies[0]->getCost(); estimate.estimatedCost += estimate.estimatedNrItems; return estimate; } // We really should not get here, but if so, do something bordering on // sensible: CostEstimate estimate = CostEstimate::empty(); estimate.estimatedNrItems = 1; estimate.estimatedCost = 1.0; return estimate; } /// @brief construct a scatter node ScatterNode::ScatterNode( ExecutionPlan* plan, arangodb::velocypack::Slice const& base ) : ExecutionNode(plan, base) { readClientsFromVelocyPack(base); } /// @brief creates corresponding ExecutionBlock std::unique_ptr ScatterNode::createBlock( ExecutionEngine& engine, std::unordered_map const& ) const { return std::make_unique( &engine, this, _clients ); } /// @brief toVelocyPack, for ScatterNode void ScatterNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { // call base class method ExecutionNode::toVelocyPackHelperGeneric(nodes, flags); // serialize clients writeClientsToVelocyPack(nodes); // And close it: nodes.close(); } bool ScatterNode::readClientsFromVelocyPack(VPackSlice base) { auto const clientsSlice = base.get("clients"); if (!clientsSlice.isArray()) { LOG_TOPIC(ERR, Logger::AQL) << "invalid serialized ScatterNode definition, 'clients' attribute is expected to be an array of string"; return false; } size_t pos = 0; for (auto const clientSlice : velocypack::ArrayIterator(clientsSlice)) { if (!clientSlice.isString()) { LOG_TOPIC(ERR, Logger::AQL) << "invalid serialized ScatterNode definition, 'clients' attribute is expected to be an array of string but got not a string at line " << pos; _clients.clear(); // clear malformed node return false; } _clients.emplace_back(clientSlice.copyString()); ++pos; } return true; } void ScatterNode::writeClientsToVelocyPack(VPackBuilder& builder) const { VPackArrayBuilder arrayScope(&builder, "clients"); for (auto const& client : _clients) { builder.add(VPackValue(client)); } } /// @brief estimateCost CostEstimate ScatterNode::estimateCost() const { CostEstimate estimate = _dependencies[0]->getCost(); estimate.estimatedCost += estimate.estimatedNrItems * _clients.size(); return estimate; } /// @brief construct a distribute node DistributeNode::DistributeNode( ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : ScatterNode(plan, base), CollectionAccessingNode(plan, base), _variable(nullptr), _alternativeVariable(nullptr), _createKeys(base.get("createKeys").getBoolean()), _allowKeyConversionToObject(base.get("allowKeyConversionToObject").getBoolean()), _allowSpecifiedKeys(false) { if (base.hasKey("variable") && base.hasKey("alternativeVariable")) { _variable = Variable::varFromVPack(plan->getAst(), base, "variable"); _alternativeVariable = Variable::varFromVPack(plan->getAst(), base, "alternativeVariable"); } else { _variable = plan->getAst()->variables()->getVariable(base.get("varId").getNumericValue()); _alternativeVariable = plan->getAst()->variables()->getVariable(base.get("alternativeVarId").getNumericValue()); } } /// @brief creates corresponding ExecutionBlock std::unique_ptr DistributeNode::createBlock( ExecutionEngine& engine, std::unordered_map const& ) const { return std::make_unique( &engine, this, clients(), collection() ); } /// @brief toVelocyPack, for DistributedNode void DistributeNode::toVelocyPackHelper(VPackBuilder& builder, unsigned flags) const { // call base class method ExecutionNode::toVelocyPackHelperGeneric(builder, flags); // add collection information CollectionAccessingNode::toVelocyPack(builder); // serialize clients writeClientsToVelocyPack(builder); builder.add("createKeys", VPackValue(_createKeys)); builder.add("allowKeyConversionToObject", VPackValue(_allowKeyConversionToObject)); builder.add(VPackValue("variable")); _variable->toVelocyPack(builder); builder.add(VPackValue("alternativeVariable")); _alternativeVariable->toVelocyPack(builder); // legacy format, remove in 3.4 builder.add("varId", VPackValue(static_cast(_variable->id))); builder.add("alternativeVarId", VPackValue(static_cast(_alternativeVariable->id))); // And close it: builder.close(); } /// @brief getVariablesUsedHere, returning a vector std::vector DistributeNode::getVariablesUsedHere() const { std::vector vars; vars.emplace_back(_variable); if (_variable != _alternativeVariable) { vars.emplace_back(_alternativeVariable); } return vars; } /// @brief getVariablesUsedHere, modifying the set in-place void DistributeNode::getVariablesUsedHere(std::unordered_set& vars) const { vars.emplace(_variable); vars.emplace(_alternativeVariable); } /// @brief estimateCost CostEstimate DistributeNode::estimateCost() const { CostEstimate estimate = _dependencies[0]->getCost(); estimate.estimatedCost += estimate.estimatedNrItems; return estimate; } /*static*/ Collection const* GatherNode::findCollection( GatherNode const& root ) noexcept { ExecutionNode const* node = root.getFirstDependency(); while (node) { switch (node->getType()) { case ENUMERATE_COLLECTION: return castTo(node)->collection(); case INDEX: return castTo(node)->collection(); case TRAVERSAL: case SHORTEST_PATH: return castTo(node)->collection(); case SCATTER: return nullptr; // diamond boundary default: node = node->getFirstDependency(); break; } } return nullptr; } /// @brief construct a gather node GatherNode::GatherNode( ExecutionPlan* plan, arangodb::velocypack::Slice const& base, SortElementVector const& elements) : ExecutionNode(plan, base), _elements(elements), _sortmode(SortMode::MinElement) { if (!_elements.empty()) { auto const sortModeSlice = base.get("sortmode"); if (!toSortMode(VelocyPackHelper::getStringRef(sortModeSlice, ""), _sortmode)) { LOG_TOPIC(ERR, Logger::AQL) << "invalid sort mode detected while creating 'GatherNode' from vpack"; } } } GatherNode::GatherNode( ExecutionPlan* plan, size_t id, SortMode sortMode) noexcept : ExecutionNode(plan, id), _sortmode(sortMode) { } /// @brief toVelocyPack, for GatherNode void GatherNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { // call base class method ExecutionNode::toVelocyPackHelperGeneric(nodes, flags); if (_elements.empty()) { nodes.add("sortmode", VPackValue(SortModeUnset.data())); } else { nodes.add("sortmode", VPackValue(toString(_sortmode).data())); } nodes.add(VPackValue("elements")); { VPackArrayBuilder guard(&nodes); for (auto const& it : _elements) { VPackObjectBuilder obj(&nodes); nodes.add(VPackValue("inVariable")); it.var->toVelocyPack(nodes); nodes.add("ascending", VPackValue(it.ascending)); if (!it.attributePath.empty()) { nodes.add(VPackValue("path")); VPackArrayBuilder arr(&nodes); for (auto const& a : it.attributePath) { nodes.add(VPackValue(a)); } } } } // And close it: nodes.close(); } /// @brief creates corresponding ExecutionBlock std::unique_ptr GatherNode::createBlock( ExecutionEngine& engine, std::unordered_map const& ) const { if (_elements.empty()) { return std::make_unique(engine, *this); } return std::make_unique(engine, *this); } /// @brief estimateCost CostEstimate GatherNode::estimateCost() const { CostEstimate estimate = _dependencies[0]->getCost(); estimate.estimatedCost += estimate.estimatedNrItems; return estimate; } SingleRemoteOperationNode::SingleRemoteOperationNode(ExecutionPlan* plan, size_t id, NodeType mode, bool replaceIndexNode, std::string const& key, Collection const* collection, ModificationOptions const& options, Variable const* in, Variable const* out, Variable const* OLD, Variable const* NEW ) : ExecutionNode(plan, id) , CollectionAccessingNode(collection) , _replaceIndexNode(replaceIndexNode) , _key(key) , _mode(mode) , _inVariable(in) , _outVariable(out) , _outVariableOld(OLD) , _outVariableNew(NEW) , _options(options) { if (_mode == NodeType::INDEX) { //select TRI_ASSERT(!_key.empty()); TRI_ASSERT(_inVariable== nullptr); TRI_ASSERT(_outVariable != nullptr); TRI_ASSERT(_outVariableOld == nullptr); TRI_ASSERT(_outVariableNew == nullptr); } else if (_mode == NodeType::REMOVE) { TRI_ASSERT(!_key.empty()); TRI_ASSERT(_inVariable == nullptr); TRI_ASSERT(_outVariable == nullptr); TRI_ASSERT(_outVariableNew == nullptr); } else if (_mode == NodeType::INSERT) { TRI_ASSERT(_key.empty()); TRI_ASSERT(_outVariable == nullptr); } else if (_mode == NodeType::UPDATE) { TRI_ASSERT(_outVariable == nullptr); } else if (_mode == NodeType::REPLACE) { TRI_ASSERT(_outVariable == nullptr); } else { TRI_ASSERT(false); } } /// @brief creates corresponding SingleRemoteOperationNode std::unique_ptr SingleRemoteOperationNode::createBlock( ExecutionEngine& engine, std::unordered_map const& ) const { return std::make_unique(&engine, this); } /// @brief toVelocyPack, for SingleRemoteOperationNode void SingleRemoteOperationNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { // call base class method ExecutionNode::toVelocyPackHelperGeneric(nodes, flags); CollectionAccessingNode::toVelocyPackHelperPrimaryIndex(nodes); // add collection information CollectionAccessingNode::toVelocyPack(nodes); nodes.add("mode", VPackValue(ExecutionNode::getTypeString(_mode))); nodes.add("replaceIndexNode", VPackValue(_replaceIndexNode)); if(!_key.empty()){ nodes.add("key", VPackValue(_key)); } // add out variables bool isAnyVarUsedLater = false; if (_outVariableOld != nullptr) { nodes.add(VPackValue("outVariableOld")); _outVariableOld->toVelocyPack(nodes); isAnyVarUsedLater |= isVarUsedLater(_outVariableOld); } if (_outVariableNew != nullptr) { nodes.add(VPackValue("outVariableNew")); _outVariableNew->toVelocyPack(nodes); isAnyVarUsedLater |= isVarUsedLater(_outVariableNew); } if (_inVariable!= nullptr) { nodes.add(VPackValue("inVariable")); _inVariable->toVelocyPack(nodes); } if (_outVariable != nullptr) { nodes.add(VPackValue("outVariable")); _outVariable->toVelocyPack(nodes); isAnyVarUsedLater |= isVarUsedLater(_outVariable); } nodes.add("producesResult", VPackValue(isAnyVarUsedLater)); nodes.add(VPackValue("modificationFlags")); _options.toVelocyPack(nodes); nodes.add("projections", VPackValue(VPackValueType::Array)); // TODO: support projections? nodes.close(); // And close it: nodes.close(); } /// @brief estimateCost CostEstimate SingleRemoteOperationNode::estimateCost() const { CostEstimate estimate = _dependencies[0]->getCost(); return estimate; }