mirror of https://gitee.com/bigwinds/arangodb
415 lines
12 KiB
C++
415 lines
12 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2014-2018 ArangoDB 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 Wilfried Goesgens
|
|
/// @author Jan Christoph Uhde
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "OptimizerRules.h"
|
|
#include "Aql/ClusterNodes.h"
|
|
#include "Aql/Condition.h"
|
|
#include "Aql/ExecutionNode.h"
|
|
#include "Aql/ExecutionPlan.h"
|
|
#include "Aql/IndexNode.h"
|
|
#include "Aql/ModificationNodes.h"
|
|
#include "Aql/Optimizer.h"
|
|
#include "Basics/StaticStrings.h"
|
|
|
|
using namespace arangodb;
|
|
using namespace arangodb::aql;
|
|
using EN = arangodb::aql::ExecutionNode;
|
|
|
|
namespace {
|
|
|
|
ExecutionNode* hasSingleParent(ExecutionNode const* in, std::vector<EN::NodeType> const& types) {
|
|
auto* parent = in->getFirstParent();
|
|
if (parent) {
|
|
for (auto const& type : types) {
|
|
if (parent->getType() == type) {
|
|
return parent;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ExecutionNode* hasSingleDep(ExecutionNode const* in, EN::NodeType const type) {
|
|
auto* dep = in->getFirstDependency();
|
|
if (dep && dep->getType() == type) {
|
|
return dep;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Index* hasSingleIndexHandle(ExecutionNode const* node) {
|
|
TRI_ASSERT(node->getType() == EN::INDEX);
|
|
IndexNode const* indexNode = ExecutionNode::castTo<IndexNode const*>(node);
|
|
auto indexHandleVec = indexNode->getIndexes();
|
|
if (indexHandleVec.size() == 1) {
|
|
return indexHandleVec.front().getIndex().get();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Index* hasSingleIndexHandle(ExecutionNode const* node, Index::IndexType type) {
|
|
auto* idx = ::hasSingleIndexHandle(node);
|
|
if (idx && idx->type() == type) {
|
|
return idx;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<AstNode const*> hasBinaryCompare(ExecutionNode const* node) {
|
|
// returns any AstNode in the expression that is
|
|
// a binary comparison.
|
|
TRI_ASSERT(node->getType() == EN::INDEX);
|
|
IndexNode const* indexNode = ExecutionNode::castTo<IndexNode const*>(node);
|
|
AstNode const* cond = indexNode->condition()->root();
|
|
std::vector<AstNode const*> result;
|
|
|
|
auto preVisitor = [&result] (AstNode const* node) {
|
|
if (node == nullptr) {
|
|
return false;
|
|
};
|
|
|
|
if(node->type == NODE_TYPE_OPERATOR_BINARY_EQ){
|
|
result.push_back(node);
|
|
return false;
|
|
}
|
|
|
|
//skip over NARY AND/OR
|
|
if(node->type == NODE_TYPE_OPERATOR_NARY_OR ||
|
|
node->type == NODE_TYPE_OPERATOR_NARY_AND) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
};
|
|
auto postVisitor = [](AstNode const*){};
|
|
Ast::traverseReadOnly(cond, preVisitor, postVisitor);
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string getFirstKey(std::vector<AstNode const*> const& compares) {
|
|
for(auto const* node : compares){
|
|
AstNode const* keyNode = node->getMemberUnchecked(0);
|
|
if (keyNode->type == AstNodeType::NODE_TYPE_ATTRIBUTE_ACCESS &&
|
|
keyNode->stringEquals(StaticStrings::KeyString)) {
|
|
keyNode = node->getMemberUnchecked(1);
|
|
}
|
|
if (keyNode->isStringValue()){
|
|
return keyNode->getString();
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
bool depIsSingletonOrConstCalc(ExecutionNode const* node) {
|
|
while (node) {
|
|
node = node->getFirstDependency();
|
|
if (node == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (node->getType() == EN::SINGLETON) {
|
|
return true;
|
|
}
|
|
|
|
if (node->getType() != EN::CALCULATION) {
|
|
return false;
|
|
}
|
|
|
|
if (!node->getVariablesUsedHere().empty()) {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool parentIsReturnOrConstCalc(ExecutionNode const* node) {
|
|
node = node->getFirstParent();
|
|
while (node) {
|
|
auto type = node->getType();
|
|
// we do not need to check the order because
|
|
// we expect a valid plan
|
|
if(type != EN::CALCULATION && type != EN::RETURN) {
|
|
return false;
|
|
}
|
|
node = node->getFirstParent();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void replaceNode(ExecutionPlan* plan, ExecutionNode* oldNode, ExecutionNode* newNode) {
|
|
if(oldNode == plan->root()) {
|
|
for(auto* dep : oldNode->getDependencies()) {
|
|
newNode->addDependency(dep);
|
|
}
|
|
plan->root(newNode,true);
|
|
} else {
|
|
// replaceNode does not seem to work well with subqueries
|
|
// if the subqueries root is replaced.
|
|
// It looks like Subquery node will still point to
|
|
// the old node.
|
|
|
|
TRI_ASSERT(oldNode != plan->root());
|
|
plan->replaceNode(oldNode, newNode);
|
|
TRI_ASSERT(!oldNode->hasDependency());
|
|
TRI_ASSERT(!oldNode->hasParent());
|
|
}
|
|
}
|
|
|
|
bool substituteClusterSingleDocumentOperationsIndex(Optimizer* opt,
|
|
ExecutionPlan* plan,
|
|
OptimizerRule const* rule) {
|
|
bool modified = false;
|
|
SmallVector<ExecutionNode*>::allocator_type::arena_type a;
|
|
SmallVector<ExecutionNode*> nodes{a};
|
|
plan->findNodesOfType(nodes, EN::INDEX, false);
|
|
|
|
if(nodes.size() != 1){
|
|
return modified;
|
|
}
|
|
|
|
for(auto* node : nodes){
|
|
|
|
if (!::depIsSingletonOrConstCalc(node)) {
|
|
continue;
|
|
}
|
|
|
|
Index* index = ::hasSingleIndexHandle(node, Index::TRI_IDX_TYPE_PRIMARY_INDEX);
|
|
if (index) {
|
|
IndexNode* indexNode = ExecutionNode::castTo<IndexNode*>(node);
|
|
auto binaryCompares = ::hasBinaryCompare(node);
|
|
std::string key = ::getFirstKey(binaryCompares);
|
|
if (key.empty()) {
|
|
continue;
|
|
}
|
|
|
|
auto* parentModification = ::hasSingleParent(node, {EN::INSERT, EN::REMOVE, EN::UPDATE, EN::REPLACE});
|
|
|
|
if (parentModification){
|
|
auto mod = ExecutionNode::castTo<ModificationNode*>(parentModification);
|
|
auto parentType = parentModification->getType();
|
|
auto const& vec = mod->getVariablesUsedHere();
|
|
|
|
Variable const* update = nullptr;
|
|
Variable const* keyVar = nullptr;
|
|
|
|
if ( parentType == EN::REMOVE) {
|
|
keyVar = vec.front();
|
|
TRI_ASSERT(vec.size() == 1);
|
|
} else {
|
|
update = vec.front();
|
|
if (vec.size() > 1) {
|
|
keyVar = vec.back();
|
|
}
|
|
}
|
|
|
|
if(keyVar && indexNode->outVariable()->id != keyVar->id){
|
|
continue;
|
|
}
|
|
|
|
if(!keyVar){
|
|
if (update && indexNode->outVariable()->id == update->id){
|
|
// the update document is already described by the key provided
|
|
// in the index condition
|
|
update = nullptr;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ExecutionNode* singleOperationNode = plan->registerNode(
|
|
new SingleRemoteOperationNode(
|
|
plan, plan->nextId(),
|
|
parentType,
|
|
true, key, mod->collection(),
|
|
mod->getOptions(),
|
|
update,
|
|
nullptr,
|
|
mod->getOutVariableOld(),
|
|
mod->getOutVariableNew()
|
|
)
|
|
);
|
|
|
|
if (!::parentIsReturnOrConstCalc(mod)) {
|
|
continue;
|
|
}
|
|
|
|
::replaceNode(plan, mod, singleOperationNode);
|
|
plan->unlinkNode(indexNode);
|
|
modified = true;
|
|
} else if (::parentIsReturnOrConstCalc(node)) {
|
|
ExecutionNode* singleOperationNode = plan->registerNode(
|
|
new SingleRemoteOperationNode(plan, plan->nextId()
|
|
,EN::INDEX, true, key, indexNode->collection(), ModificationOptions{}
|
|
, nullptr /*in*/ , indexNode->outVariable() /*out*/, nullptr /*old*/, nullptr /*new*/)
|
|
);
|
|
::replaceNode(plan, indexNode, singleOperationNode);
|
|
modified = true;
|
|
|
|
}
|
|
}
|
|
}
|
|
return modified;
|
|
}
|
|
|
|
bool substituteClusterSingleDocumentOperationsNoIndex(Optimizer* opt,
|
|
ExecutionPlan* plan,
|
|
OptimizerRule const* rule) {
|
|
bool modified = false;
|
|
SmallVector<ExecutionNode*>::allocator_type::arena_type a;
|
|
SmallVector<ExecutionNode*> nodes{a};
|
|
plan->findNodesOfType(nodes, {EN::INSERT, EN::REMOVE, EN::UPDATE, EN::REPLACE}, false);
|
|
|
|
if (nodes.size() != 1) {
|
|
return modified;
|
|
}
|
|
|
|
for (auto* node : nodes) {
|
|
auto mod = ExecutionNode::castTo<ModificationNode*>(node);
|
|
|
|
if (!::depIsSingletonOrConstCalc(node)) {
|
|
continue;
|
|
}
|
|
|
|
if (!::parentIsReturnOrConstCalc (node)) {
|
|
continue;
|
|
}
|
|
|
|
auto depType = mod->getType();
|
|
Variable const* update = nullptr;
|
|
Variable const* keyVar = nullptr;
|
|
std::string key = "";
|
|
auto const& vec = mod->getVariablesUsedHere();
|
|
|
|
if ( depType == EN::REMOVE) {
|
|
keyVar = vec.front();
|
|
TRI_ASSERT(vec.size() == 1);
|
|
} else {
|
|
update = vec.front(); // this can be same as keyvar!
|
|
// this use is possible because we
|
|
// not delete the variable's setter
|
|
if (vec.size() > 1) {
|
|
keyVar = vec.back();
|
|
}
|
|
}
|
|
|
|
ExecutionNode* cursor = node;
|
|
CalculationNode* calc = nullptr;
|
|
|
|
if (keyVar) {
|
|
std::unordered_set<Variable const*> keySet;
|
|
keySet.emplace(keyVar);
|
|
|
|
while (cursor) {
|
|
cursor = ::hasSingleDep(cursor, EN::CALCULATION);
|
|
if (cursor) {
|
|
CalculationNode* c = ExecutionNode::castTo<CalculationNode*>(cursor);
|
|
if (c->setsVariable(keySet)) {
|
|
calc = c;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!calc) {
|
|
continue;
|
|
}
|
|
AstNode const* expr = calc->expression()->node();
|
|
if (expr->isStringValue()) {
|
|
key = expr->getString();
|
|
} else if (expr->isObject()) {
|
|
bool foundKey = false;
|
|
for (std::size_t i = 0 ; i < expr->numMembers(); i++) {
|
|
auto* anode = expr->getMemberUnchecked(i);
|
|
if (anode->getString() == StaticStrings::KeyString) {
|
|
key = anode->getMember(0)->getString();
|
|
foundKey = true;
|
|
}
|
|
if (anode->getString() == StaticStrings::RevString) {
|
|
foundKey = false; // decline if _rev is in the game
|
|
break;
|
|
}
|
|
}
|
|
if (!foundKey) {
|
|
continue;
|
|
}
|
|
} else {
|
|
// write more code here if we
|
|
// want to support thinks like:
|
|
//
|
|
// DOCUMENT("foo/bar")
|
|
}
|
|
|
|
if (key.empty()) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!::depIsSingletonOrConstCalc(cursor)) {
|
|
continue;
|
|
}
|
|
|
|
ExecutionNode* singleOperationNode = plan->registerNode(
|
|
new SingleRemoteOperationNode(
|
|
plan, plan->nextId(),
|
|
depType,
|
|
false, key, mod->collection(),
|
|
mod->getOptions(),
|
|
update /*in*/,
|
|
nullptr,
|
|
mod->getOutVariableOld(),
|
|
mod->getOutVariableNew()
|
|
)
|
|
);
|
|
|
|
::replaceNode(plan, mod, singleOperationNode);
|
|
|
|
if (calc) {
|
|
plan->unlinkNode(calc);
|
|
}
|
|
modified = true;
|
|
} // for node : nodes
|
|
|
|
return modified;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void arangodb::aql::substituteClusterSingleDocumentOperations(Optimizer* opt,
|
|
std::unique_ptr<ExecutionPlan> plan,
|
|
OptimizerRule const* rule) {
|
|
bool modified = false;
|
|
|
|
for (auto const& fun : { &::substituteClusterSingleDocumentOperationsIndex
|
|
, &::substituteClusterSingleDocumentOperationsNoIndex
|
|
}) {
|
|
modified = fun(opt, plan.get(), rule);
|
|
if (modified) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
opt->addPlan(std::move(plan), rule, modified);
|
|
}
|