//////////////////////////////////////////////////////////////////////////////// /// 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 "ModificationNodes.h" #include "Aql/Ast.h" #include "Aql/Collection.h" #include "Aql/ExecutionBlockImpl.h" #include "Aql/ExecutionPlan.h" #include "Aql/ModificationExecutor.h" #include "Aql/ModificationExecutorTraits.h" #include "Aql/Query.h" #include "Aql/VariableGenerator.h" using namespace arangodb::aql; ModificationNode::ModificationNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : ExecutionNode(plan, base), CollectionAccessingNode(plan, base), _options(base), _outVariableOld( Variable::varFromVPack(plan->getAst(), base, "outVariableOld", true)), _outVariableNew( Variable::varFromVPack(plan->getAst(), base, "outVariableNew", true)), _countStats(base.get("countStats").getBool()), _producesResults(base.hasKey("producesResults") ? base.get("producesResults").getBool() : true) {} /// @brief toVelocyPack void ModificationNode::toVelocyPackHelper(VPackBuilder& builder, unsigned flags) const { // call base class method ExecutionNode::toVelocyPackHelperGeneric(builder, flags); // add collection information CollectionAccessingNode::toVelocyPack(builder); // Now put info about vocbase and cid in there builder.add("countStats", VPackValue(_countStats)); builder.add("producesResults", VPackValue(_producesResults)); // add out variables if (_outVariableOld != nullptr) { builder.add(VPackValue("outVariableOld")); _outVariableOld->toVelocyPack(builder); } if (_outVariableNew != nullptr) { builder.add(VPackValue("outVariableNew")); _outVariableNew->toVelocyPack(builder); } builder.add(VPackValue("modificationFlags")); _options.toVelocyPack(builder); } /// @brief estimateCost /// Note that all the modifying nodes use this estimateCost method which is /// why we can make it final here. CostEstimate ModificationNode::estimateCost() const { CostEstimate estimate = _dependencies.at(0)->getCost(); estimate.estimatedCost += estimate.estimatedNrItems; if (_outVariableOld == nullptr && _outVariableNew == nullptr && !_producesResults) { // node produces no output estimate.estimatedNrItems = 0; } return estimate; } void ModificationNode::cloneCommon(ModificationNode* c) const { if (!_countStats) { c->disableStatistics(); } c->producesResults(_producesResults); } RemoveNode::RemoveNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : ModificationNode(plan, base), _inVariable(Variable::varFromVPack(plan->getAst(), base, "inVariable")) {} void RemoveNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { ModificationNode::toVelocyPackHelper(nodes, flags); ModificationNode::toVelocyPackHelperPrimaryIndex(nodes); nodes.add(VPackValue("inVariable")); _inVariable->toVelocyPack(nodes); // And close it: nodes.close(); } /// @brief creates corresponding ExecutionBlock std::unique_ptr RemoveNode::createBlock( ExecutionEngine& engine, std::unordered_map const&) const { ExecutionNode const* previousNode = getFirstDependency(); TRI_ASSERT(previousNode != nullptr); RegisterId inDocRegister = variableToRegisterId(_inVariable); RegisterId outputNew = variableToRegisterOptionalId(_outVariableNew); RegisterId outputOld = variableToRegisterOptionalId(_outVariableOld); OperationOptions options = convertOptions(_options, _outVariableNew, _outVariableOld); ModificationExecutorInfos infos( inDocRegister, ExecutionNode::MaxRegisterId, ExecutionNode::MaxRegisterId, outputNew, outputOld, ExecutionNode::MaxRegisterId /*output*/, getRegisterPlan()->nrRegs[previousNode->getDepth()] /*nr input regs*/, getRegisterPlan()->nrRegs[getDepth()] /*nr output regs*/, getRegsToClear(), calcRegsToKeep(), _plan->getAst()->query()->trx(), std::move(options), _collection, ProducesResults(producesResults()), ConsultAqlWriteFilter(_options.consultAqlWriteFilter), IgnoreErrors(_options.ignoreErrors), DoCount(countStats()), IsReplace(false) /*(needed by upsert)*/, IgnoreDocumentNotFound(_options.ignoreDocumentNotFound)); if (_options.readCompleteInput) { return std::make_unique>>( &engine, this, std::move(infos)); } else { return std::make_unique>>>( &engine, this, std::move(infos)); } } /// @brief clone ExecutionNode recursively ExecutionNode* RemoveNode::clone(ExecutionPlan* plan, bool withDependencies, bool withProperties) const { auto outVariableOld = _outVariableOld; auto inVariable = _inVariable; if (withProperties) { if (_outVariableOld != nullptr) { outVariableOld = plan->getAst()->variables()->createVariable(outVariableOld); } inVariable = plan->getAst()->variables()->createVariable(inVariable); } auto c = std::make_unique(plan, _id, _collection, _options, inVariable, outVariableOld); ModificationNode::cloneCommon(c.get()); return cloneHelper(std::move(c), withDependencies, withProperties); } InsertNode::InsertNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : ModificationNode(plan, base), _inVariable(Variable::varFromVPack(plan->getAst(), base, "inVariable")) {} /// @brief toVelocyPack void InsertNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { ModificationNode::toVelocyPackHelper(nodes, flags); // call base class method // Now put info about vocbase and cid in there nodes.add(VPackValue("inVariable")); _inVariable->toVelocyPack(nodes); // And close it: nodes.close(); } /// @brief creates corresponding ExecutionBlock std::unique_ptr InsertNode::createBlock( ExecutionEngine& engine, std::unordered_map const&) const { using namespace arangodb::aql; ExecutionNode const* previousNode = getFirstDependency(); TRI_ASSERT(previousNode != nullptr); RegisterId inputRegister = variableToRegisterId(_inVariable); RegisterId outputNew = variableToRegisterOptionalId(_outVariableNew); RegisterId outputOld = variableToRegisterOptionalId(_outVariableOld); OperationOptions options = convertOptions(_options, _outVariableNew, _outVariableOld); ModificationExecutorInfos infos( inputRegister, ExecutionNode::MaxRegisterId, ExecutionNode::MaxRegisterId, outputNew, outputOld, ExecutionNode::MaxRegisterId /*output*/, getRegisterPlan()->nrRegs[previousNode->getDepth()] /*nr input regs*/, getRegisterPlan()->nrRegs[getDepth()] /*nr output regs*/, getRegsToClear(), calcRegsToKeep(), _plan->getAst()->query()->trx(), std::move(options), _collection, ProducesResults(producesResults()), ConsultAqlWriteFilter(_options.consultAqlWriteFilter), IgnoreErrors(_options.ignoreErrors), DoCount(countStats()), IsReplace(false) /*(needed by upsert)*/, IgnoreDocumentNotFound(_options.ignoreDocumentNotFound)); if (_options.readCompleteInput) { return std::make_unique>>( &engine, this, std::move(infos)); } else { return std::make_unique>>>( &engine, this, std::move(infos)); } } /// @brief clone ExecutionNode recursively ExecutionNode* InsertNode::clone(ExecutionPlan* plan, bool withDependencies, bool withProperties) const { auto outVariableOld = _outVariableOld; auto outVariableNew = _outVariableNew; auto inVariable = _inVariable; if (withProperties) { if (_outVariableNew != nullptr) { outVariableNew = plan->getAst()->variables()->createVariable(outVariableNew); } if (_outVariableOld != nullptr) { outVariableOld = plan->getAst()->variables()->createVariable(outVariableOld); } inVariable = plan->getAst()->variables()->createVariable(inVariable); } auto c = std::make_unique(plan, _id, _collection, _options, inVariable, outVariableOld, outVariableNew); ModificationNode::cloneCommon(c.get()); return cloneHelper(std::move(c), withDependencies, withProperties); } UpdateReplaceNode::UpdateReplaceNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : ModificationNode(plan, base), _inDocVariable( Variable::varFromVPack(plan->getAst(), base, "inDocVariable")), _inKeyVariable( Variable::varFromVPack(plan->getAst(), base, "inKeyVariable", true)) {} void UpdateReplaceNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { ModificationNode::toVelocyPackHelper(nodes, flags); nodes.add(VPackValue("inDocVariable")); _inDocVariable->toVelocyPack(nodes); // inKeyVariable might be empty if (_inKeyVariable != nullptr) { nodes.add(VPackValue("inKeyVariable")); _inKeyVariable->toVelocyPack(nodes); } } UpdateNode::UpdateNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : UpdateReplaceNode(plan, base) {} /// @brief toVelocyPack void UpdateNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { UpdateReplaceNode::toVelocyPackHelper(nodes, flags); ModificationNode::toVelocyPackHelperPrimaryIndex(nodes); nodes.close(); } /// @brief creates corresponding ExecutionBlock std::unique_ptr UpdateNode::createBlock( ExecutionEngine& engine, std::unordered_map const&) const { using namespace arangodb::aql; ExecutionNode const* previousNode = getFirstDependency(); TRI_ASSERT(previousNode != nullptr); RegisterId inDocRegister = variableToRegisterId(_inDocVariable); RegisterId inKeyRegister = variableToRegisterOptionalId(_inKeyVariable); RegisterId outputNew = variableToRegisterOptionalId(_outVariableNew); RegisterId outputOld = variableToRegisterOptionalId(_outVariableOld); OperationOptions options = convertOptions(_options, _outVariableNew, _outVariableOld); ModificationExecutorInfos infos( inDocRegister, inKeyRegister, ExecutionNode::MaxRegisterId, outputNew, outputOld, ExecutionNode::MaxRegisterId /*output*/, getRegisterPlan()->nrRegs[previousNode->getDepth()] /*nr input regs*/, getRegisterPlan()->nrRegs[getDepth()] /*nr output regs*/, getRegsToClear(), calcRegsToKeep(), _plan->getAst()->query()->trx(), std::move(options), _collection, ProducesResults(producesResults()), ConsultAqlWriteFilter(_options.consultAqlWriteFilter), IgnoreErrors(_options.ignoreErrors), DoCount(countStats()), IsReplace(false) /*(needed by upsert)*/, IgnoreDocumentNotFound(_options.ignoreDocumentNotFound)); if (_options.readCompleteInput) { return std::make_unique>>( &engine, this, std::move(infos)); } else { return std::make_unique>>>( &engine, this, std::move(infos)); } } /// @brief clone ExecutionNode recursively ExecutionNode* UpdateNode::clone(ExecutionPlan* plan, bool withDependencies, bool withProperties) const { auto outVariableOld = _outVariableOld; auto outVariableNew = _outVariableNew; auto inKeyVariable = _inKeyVariable; auto inDocVariable = _inDocVariable; if (withProperties) { if (_outVariableOld != nullptr) { outVariableOld = plan->getAst()->variables()->createVariable(outVariableOld); } if (_outVariableNew != nullptr) { outVariableNew = plan->getAst()->variables()->createVariable(outVariableNew); } if (inKeyVariable != nullptr) { inKeyVariable = plan->getAst()->variables()->createVariable(inKeyVariable); } inDocVariable = plan->getAst()->variables()->createVariable(inDocVariable); } auto c = std::make_unique(plan, _id, _collection, _options, inDocVariable, inKeyVariable, outVariableOld, outVariableNew); ModificationNode::cloneCommon(c.get()); return cloneHelper(std::move(c), withDependencies, withProperties); } ReplaceNode::ReplaceNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : UpdateReplaceNode(plan, base) {} /// @brief toVelocyPack void ReplaceNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { UpdateReplaceNode::toVelocyPackHelper(nodes, flags); ModificationNode::toVelocyPackHelperPrimaryIndex(nodes); nodes.close(); } /// @brief creates corresponding ExecutionBlock std::unique_ptr ReplaceNode::createBlock( ExecutionEngine& engine, std::unordered_map const&) const { ExecutionNode const* previousNode = getFirstDependency(); TRI_ASSERT(previousNode != nullptr); RegisterId inDocRegister = variableToRegisterId(_inDocVariable); RegisterId inKeyRegister = variableToRegisterOptionalId(_inKeyVariable); RegisterId outputNew = variableToRegisterOptionalId(_outVariableNew); RegisterId outputOld = variableToRegisterOptionalId(_outVariableOld); OperationOptions options = convertOptions(_options, _outVariableNew, _outVariableOld); ModificationExecutorInfos infos( inDocRegister, inKeyRegister, ExecutionNode::MaxRegisterId, outputNew, outputOld, ExecutionNode::MaxRegisterId /*output*/, getRegisterPlan()->nrRegs[previousNode->getDepth()] /*nr input regs*/, getRegisterPlan()->nrRegs[getDepth()] /*nr output regs*/, getRegsToClear(), calcRegsToKeep(), _plan->getAst()->query()->trx(), std::move(options), _collection, ProducesResults(producesResults()), ConsultAqlWriteFilter(_options.consultAqlWriteFilter), IgnoreErrors(_options.ignoreErrors), DoCount(countStats()), IsReplace(false) /*(needed by upsert)*/, IgnoreDocumentNotFound(_options.ignoreDocumentNotFound)); if (_options.readCompleteInput) { return std::make_unique>>( &engine, this, std::move(infos)); } else { return std::make_unique>>>( &engine, this, std::move(infos)); } } /// @brief clone ExecutionNode recursively ExecutionNode* ReplaceNode::clone(ExecutionPlan* plan, bool withDependencies, bool withProperties) const { auto outVariableOld = _outVariableOld; auto outVariableNew = _outVariableNew; auto inKeyVariable = _inKeyVariable; auto inDocVariable = _inDocVariable; if (withProperties) { if (_outVariableOld != nullptr) { outVariableOld = plan->getAst()->variables()->createVariable(outVariableOld); } if (_outVariableNew != nullptr) { outVariableNew = plan->getAst()->variables()->createVariable(outVariableNew); } if (inKeyVariable != nullptr) { inKeyVariable = plan->getAst()->variables()->createVariable(inKeyVariable); } inDocVariable = plan->getAst()->variables()->createVariable(inDocVariable); } auto c = std::make_unique(plan, _id, _collection, _options, inDocVariable, inKeyVariable, outVariableOld, outVariableNew); ModificationNode::cloneCommon(c.get()); return cloneHelper(std::move(c), withDependencies, withProperties); } UpsertNode::UpsertNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base) : ModificationNode(plan, base), _inDocVariable( Variable::varFromVPack(plan->getAst(), base, "inDocVariable")), _insertVariable( Variable::varFromVPack(plan->getAst(), base, "insertVariable")), _updateVariable( Variable::varFromVPack(plan->getAst(), base, "updateVariable")), _isReplace(base.get("isReplace").getBoolean()) {} /// @brief toVelocyPack void UpsertNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const { ModificationNode::toVelocyPackHelper(nodes, flags); ModificationNode::toVelocyPackHelperPrimaryIndex(nodes); nodes.add(VPackValue("inDocVariable")); _inDocVariable->toVelocyPack(nodes); nodes.add(VPackValue("insertVariable")); _insertVariable->toVelocyPack(nodes); nodes.add(VPackValue("updateVariable")); _updateVariable->toVelocyPack(nodes); nodes.add("isReplace", VPackValue(_isReplace)); // And close it: nodes.close(); } /// @brief creates corresponding ExecutionBlock std::unique_ptr UpsertNode::createBlock( ExecutionEngine& engine, std::unordered_map const&) const { using namespace arangodb::aql; ExecutionNode const* previousNode = getFirstDependency(); TRI_ASSERT(previousNode != nullptr); RegisterId inDoc = variableToRegisterId(_inDocVariable); RegisterId insert = variableToRegisterId(_insertVariable); RegisterId update = variableToRegisterId(_updateVariable); RegisterId outputNew = variableToRegisterOptionalId(_outVariableNew); RegisterId outputOld = variableToRegisterOptionalId(_outVariableOld); OperationOptions options = convertOptions(_options, _outVariableNew, _outVariableOld); ModificationExecutorInfos infos( inDoc, insert, update, outputNew, outputOld, ExecutionNode::MaxRegisterId /*output*/, getRegisterPlan()->nrRegs[previousNode->getDepth()] /*nr input regs*/, getRegisterPlan()->nrRegs[getDepth()] /*nr output regs*/, getRegsToClear(), calcRegsToKeep(), _plan->getAst()->query()->trx(), std::move(options), _collection, ProducesResults(producesResults()), ConsultAqlWriteFilter(_options.consultAqlWriteFilter), IgnoreErrors(_options.ignoreErrors), DoCount(countStats()), IsReplace(_isReplace) /*(needed by upsert)*/, IgnoreDocumentNotFound(_options.ignoreDocumentNotFound)); if (_options.readCompleteInput) { return std::make_unique>>( &engine, this, std::move(infos)); } else { return std::make_unique>>>( &engine, this, std::move(infos)); } } /// @brief clone ExecutionNode recursively ExecutionNode* UpsertNode::clone(ExecutionPlan* plan, bool withDependencies, bool withProperties) const { auto outVariableNew = _outVariableNew; auto inDocVariable = _inDocVariable; auto insertVariable = _insertVariable; auto updateVariable = _updateVariable; if (withProperties) { if (_outVariableNew != nullptr) { outVariableNew = plan->getAst()->variables()->createVariable(outVariableNew); } inDocVariable = plan->getAst()->variables()->createVariable(inDocVariable); insertVariable = plan->getAst()->variables()->createVariable(insertVariable); updateVariable = plan->getAst()->variables()->createVariable(updateVariable); } auto c = std::make_unique(plan, _id, _collection, _options, inDocVariable, insertVariable, updateVariable, outVariableNew, _isReplace); ModificationNode::cloneCommon(c.get()); return cloneHelper(std::move(c), withDependencies, withProperties); }