mirror of https://gitee.com/bigwinds/arangodb
parent
b76f8adc0f
commit
a14386267a
|
@ -1358,6 +1358,33 @@ AstNode* Ast::createNodeShortestPath(AstNode const* outVars, AstNode const* grap
|
|||
return node;
|
||||
}
|
||||
|
||||
/// @brief create an AST k-shortest paths node
|
||||
AstNode* Ast::createNodeKShortestPaths(AstNode const* outVars, AstNode const* graphInfo) {
|
||||
TRI_ASSERT(outVars->type == NODE_TYPE_ARRAY);
|
||||
TRI_ASSERT(graphInfo->type == NODE_TYPE_ARRAY);
|
||||
AstNode* node = createNode(NODE_TYPE_K_SHORTEST_PATHS);
|
||||
node->reserve(outVars->numMembers() + graphInfo->numMembers());
|
||||
|
||||
TRI_ASSERT(graphInfo->numMembers() == 5);
|
||||
TRI_ASSERT(outVars->numMembers() > 0);
|
||||
TRI_ASSERT(outVars->numMembers() < 3);
|
||||
|
||||
// Add GraphInfo
|
||||
for (size_t i = 0; i < graphInfo->numMembers(); ++i) {
|
||||
node->addMember(graphInfo->getMemberUnchecked(i));
|
||||
}
|
||||
|
||||
// Add variables
|
||||
for (size_t i = 0; i < outVars->numMembers(); ++i) {
|
||||
node->addMember(outVars->getMemberUnchecked(i));
|
||||
}
|
||||
TRI_ASSERT(node->numMembers() == graphInfo->numMembers() + outVars->numMembers());
|
||||
|
||||
_containsTraversal = true;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
AstNode const* Ast::createNodeOptions(AstNode const* options) const {
|
||||
if (options != nullptr) {
|
||||
return options;
|
||||
|
|
|
@ -366,6 +366,9 @@ class Ast {
|
|||
/// @brief create an AST shortest path node
|
||||
AstNode* createNodeShortestPath(AstNode const*, AstNode const*);
|
||||
|
||||
/// @brief create an AST k-shortest paths node
|
||||
AstNode* createNodeKShortestPaths(AstNode const*, AstNode const*);
|
||||
|
||||
/// @brief create an AST function call node
|
||||
AstNode* createNodeFunctionCall(char const* functionName, AstNode const* arguments) {
|
||||
return createNodeFunctionCall(functionName, strlen(functionName), arguments);
|
||||
|
|
|
@ -158,6 +158,7 @@ std::unordered_map<int, std::string const> const AstNode::TypeNames{
|
|||
"array compare not in"},
|
||||
{static_cast<int>(NODE_TYPE_QUANTIFIER), "quantifier"},
|
||||
{static_cast<int>(NODE_TYPE_SHORTEST_PATH), "shortest path"},
|
||||
{static_cast<int>(NODE_TYPE_K_SHORTEST_PATHS), "k-shortest paths"},
|
||||
{static_cast<int>(NODE_TYPE_VIEW), "view"},
|
||||
{static_cast<int>(NODE_TYPE_PARAMETER_DATASOURCE), "datasource parameter"},
|
||||
{static_cast<int>(NODE_TYPE_FOR_VIEW), "view enumeration"},
|
||||
|
@ -570,6 +571,7 @@ AstNode::AstNode(Ast* ast, arangodb::velocypack::Slice const& slice)
|
|||
case NODE_TYPE_DISTINCT:
|
||||
case NODE_TYPE_TRAVERSAL:
|
||||
case NODE_TYPE_SHORTEST_PATH:
|
||||
case NODE_TYPE_K_SHORTEST_PATHS:
|
||||
case NODE_TYPE_DIRECTION:
|
||||
case NODE_TYPE_COLLECTION_LIST:
|
||||
case NODE_TYPE_OPERATOR_NARY_AND:
|
||||
|
@ -690,6 +692,7 @@ AstNode::AstNode(std::function<void(AstNode*)> const& registerNode,
|
|||
case NODE_TYPE_DISTINCT:
|
||||
case NODE_TYPE_TRAVERSAL:
|
||||
case NODE_TYPE_SHORTEST_PATH:
|
||||
case NODE_TYPE_K_SHORTEST_PATHS:
|
||||
case NODE_TYPE_DIRECTION:
|
||||
case NODE_TYPE_COLLECTION_LIST:
|
||||
case NODE_TYPE_PASSTHRU:
|
||||
|
@ -2334,6 +2337,7 @@ void AstNode::findVariableAccess(std::vector<AstNode const*>& currentPath,
|
|||
case NODE_TYPE_DISTINCT:
|
||||
case NODE_TYPE_TRAVERSAL:
|
||||
case NODE_TYPE_SHORTEST_PATH:
|
||||
case NODE_TYPE_K_SHORTEST_PATHS:
|
||||
case NODE_TYPE_COLLECTION_LIST:
|
||||
case NODE_TYPE_DIRECTION:
|
||||
case NODE_TYPE_WITH:
|
||||
|
@ -2507,6 +2511,7 @@ AstNode const* AstNode::findReference(AstNode const* findme) const {
|
|||
case NODE_TYPE_DISTINCT:
|
||||
case NODE_TYPE_TRAVERSAL:
|
||||
case NODE_TYPE_SHORTEST_PATH:
|
||||
case NODE_TYPE_K_SHORTEST_PATHS:
|
||||
case NODE_TYPE_COLLECTION_LIST:
|
||||
case NODE_TYPE_DIRECTION:
|
||||
case NODE_TYPE_OPERATOR_NARY_AND:
|
||||
|
|
|
@ -204,9 +204,10 @@ enum AstNodeType : uint32_t {
|
|||
NODE_TYPE_QUANTIFIER = 73,
|
||||
NODE_TYPE_WITH = 74,
|
||||
NODE_TYPE_SHORTEST_PATH = 75,
|
||||
NODE_TYPE_VIEW = 76,
|
||||
NODE_TYPE_PARAMETER_DATASOURCE = 77,
|
||||
NODE_TYPE_FOR_VIEW = 78,
|
||||
NODE_TYPE_K_SHORTEST_PATHS = 76,
|
||||
NODE_TYPE_VIEW = 77,
|
||||
NODE_TYPE_PARAMETER_DATASOURCE = 78,
|
||||
NODE_TYPE_FOR_VIEW = 79,
|
||||
};
|
||||
|
||||
static_assert(NODE_TYPE_VALUE < NODE_TYPE_ARRAY, "incorrect node types order");
|
||||
|
|
|
@ -42,6 +42,7 @@ bool ConditionFinder::before(ExecutionNode* en) {
|
|||
case EN::INDEX:
|
||||
case EN::RETURN:
|
||||
case EN::TRAVERSAL:
|
||||
case EN::K_SHORTEST_PATHS:
|
||||
case EN::SHORTEST_PATH:
|
||||
case EN::ENUMERATE_IRESEARCH_VIEW:
|
||||
{
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "Aql/IResearchViewExecutor.h"
|
||||
#include "Aql/IdExecutor.h"
|
||||
#include "Aql/IndexExecutor.h"
|
||||
#include "Aql/KShortestPathsExecutor.h"
|
||||
#include "Aql/LimitExecutor.h"
|
||||
#include "Aql/ModificationExecutor.h"
|
||||
#include "Aql/ModificationExecutorTraits.h"
|
||||
|
@ -298,6 +299,19 @@ std::pair<ExecutionState, Result> ExecutionBlockImpl<ShortestPathExecutor>::shut
|
|||
return this->executor().shutdown(errorCode);
|
||||
}
|
||||
|
||||
|
||||
template <>
|
||||
std::pair<ExecutionState, Result> ExecutionBlockImpl<KShortestPathsExecutor>::shutdown(int errorCode) {
|
||||
ExecutionState state;
|
||||
Result result;
|
||||
|
||||
std::tie(state, result) = ExecutionBlock::shutdown(errorCode);
|
||||
if (state == ExecutionState::WAITING) {
|
||||
return {state, result};
|
||||
}
|
||||
return this->executor().shutdown(errorCode);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::pair<ExecutionState, Result> ExecutionBlockImpl<SubqueryExecutor>::shutdown(int errorCode) {
|
||||
ExecutionState state;
|
||||
|
@ -422,6 +436,7 @@ template class ::arangodb::aql::ExecutionBlockImpl<NoResultsExecutor>;
|
|||
template class ::arangodb::aql::ExecutionBlockImpl<ReturnExecutor<false>>;
|
||||
template class ::arangodb::aql::ExecutionBlockImpl<ReturnExecutor<true>>;
|
||||
template class ::arangodb::aql::ExecutionBlockImpl<ShortestPathExecutor>;
|
||||
template class ::arangodb::aql::ExecutionBlockImpl<KShortestPathsExecutor>;
|
||||
template class ::arangodb::aql::ExecutionBlockImpl<SortedCollectExecutor>;
|
||||
template class ::arangodb::aql::ExecutionBlockImpl<SortExecutor>;
|
||||
template class ::arangodb::aql::ExecutionBlockImpl<SubqueryExecutor>;
|
||||
|
|
|
@ -369,6 +369,7 @@ struct CoordinatorInstanciator final : public WalkerWorker<ExecutionNode> {
|
|||
break;
|
||||
case ExecutionNode::TRAVERSAL:
|
||||
case ExecutionNode::SHORTEST_PATH:
|
||||
case ExecutionNode::K_SHORTEST_PATHS:
|
||||
_dbserverParts.addGraphNode(ExecutionNode::castTo<GraphNode*>(en));
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "Aql/IResearchViewNode.h"
|
||||
#include "Aql/IdExecutor.h"
|
||||
#include "Aql/IndexNode.h"
|
||||
#include "Aql/KShortestPathsNode.h"
|
||||
#include "Aql/LimitExecutor.h"
|
||||
#include "Aql/ModificationNodes.h"
|
||||
#include "Aql/NoResultsExecutor.h"
|
||||
|
@ -94,6 +95,7 @@ std::unordered_map<int, std::string const> const typeNames{
|
|||
{static_cast<int>(ExecutionNode::UPSERT), "UpsertNode"},
|
||||
{static_cast<int>(ExecutionNode::TRAVERSAL), "TraversalNode"},
|
||||
{static_cast<int>(ExecutionNode::SHORTEST_PATH), "ShortestPathNode"},
|
||||
{static_cast<int>(ExecutionNode::K_SHORTEST_PATHS), "KShortestPathsNode"},
|
||||
{static_cast<int>(ExecutionNode::REMOTESINGLE),
|
||||
"SingleRemoteOperationNode"},
|
||||
{static_cast<int>(ExecutionNode::ENUMERATE_IRESEARCH_VIEW),
|
||||
|
@ -322,6 +324,8 @@ ExecutionNode* ExecutionNode::fromVPackFactory(ExecutionPlan* plan, VPackSlice c
|
|||
return new TraversalNode(plan, slice);
|
||||
case SHORTEST_PATH:
|
||||
return new ShortestPathNode(plan, slice);
|
||||
case K_SHORTEST_PATHS:
|
||||
return new KShortestPathsNode(plan, slice);
|
||||
case REMOTESINGLE:
|
||||
return new SingleRemoteOperationNode(plan, slice);
|
||||
case ENUMERATE_IRESEARCH_VIEW:
|
||||
|
@ -1011,7 +1015,8 @@ void ExecutionNode::RegisterPlan::after(ExecutionNode* en) {
|
|||
}
|
||||
|
||||
case ExecutionNode::TRAVERSAL:
|
||||
case ExecutionNode::SHORTEST_PATH: {
|
||||
case ExecutionNode::SHORTEST_PATH:
|
||||
case ExecutionNode::K_SHORTEST_PATHS: {
|
||||
depth++;
|
||||
auto ep = dynamic_cast<GraphNode const*>(en);
|
||||
if (ep == nullptr) {
|
||||
|
|
|
@ -144,7 +144,8 @@ class ExecutionNode {
|
|||
TRAVERSAL = 22,
|
||||
INDEX = 23,
|
||||
SHORTEST_PATH = 24,
|
||||
REMOTESINGLE = 25,
|
||||
K_SHORTEST_PATHS = 25,
|
||||
REMOTESINGLE = 26,
|
||||
ENUMERATE_IRESEARCH_VIEW,
|
||||
MAX_NODE_TYPE_VALUE
|
||||
};
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "Aql/Function.h"
|
||||
#include "Aql/IResearchViewNode.h"
|
||||
#include "Aql/IndexHint.h"
|
||||
#include "Aql/KShortestPathsNode.h"
|
||||
#include "Aql/ModificationNodes.h"
|
||||
#include "Aql/NodeFinder.h"
|
||||
#include "Aql/OptimizerRulesFeature.h"
|
||||
|
@ -1121,6 +1122,40 @@ ExecutionNode* ExecutionPlan::fromNodeShortestPath(ExecutionNode* previous,
|
|||
return addDependency(previous, en);
|
||||
}
|
||||
|
||||
/// @brief create an execution plan element from an AST for SHORTEST_PATH node
|
||||
ExecutionNode* ExecutionPlan::fromNodeKShortestPaths(ExecutionNode* previous,
|
||||
AstNode const* node) {
|
||||
TRI_ASSERT(node != nullptr && node->type == NODE_TYPE_K_SHORTEST_PATHS);
|
||||
TRI_ASSERT(node->numMembers() >= 6);
|
||||
TRI_ASSERT(node->numMembers() <= 7);
|
||||
|
||||
// the first 4 members are used by shortest_path internally.
|
||||
// The members 5-6, where 6 is optional, are used
|
||||
// as out variables.
|
||||
AstNode const* direction = node->getMember(0);
|
||||
TRI_ASSERT(direction->isIntValue());
|
||||
AstNode const* start = parseTraversalVertexNode(previous, node->getMember(1));
|
||||
AstNode const* target = parseTraversalVertexNode(previous, node->getMember(2));
|
||||
AstNode const* graph = node->getMember(3);
|
||||
|
||||
// FIXME: here goes the parameters with k etc
|
||||
auto options = createShortestPathOptions(getAst()->query(), node->getMember(4));
|
||||
|
||||
// First create the node
|
||||
auto spNode = new KShortestPathsNode(this, nextId(), &(_ast->query()->vocbase()), direction,
|
||||
start, target, graph, std::move(options));
|
||||
|
||||
auto variable = node->getMember(5);
|
||||
TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE);
|
||||
auto v = static_cast<Variable*>(variable->getData());
|
||||
TRI_ASSERT(v != nullptr);
|
||||
spNode->setPathOutput(v);
|
||||
|
||||
ExecutionNode* en = registerNode(spNode);
|
||||
TRI_ASSERT(en != nullptr);
|
||||
return addDependency(previous, en);
|
||||
}
|
||||
|
||||
/// @brief create an execution plan element from an AST FILTER node
|
||||
ExecutionNode* ExecutionPlan::fromNodeFilter(ExecutionNode* previous, AstNode const* node) {
|
||||
TRI_ASSERT(node != nullptr);
|
||||
|
@ -1881,6 +1916,10 @@ ExecutionNode* ExecutionPlan::fromNode(AstNode const* node) {
|
|||
break;
|
||||
}
|
||||
|
||||
case NODE_TYPE_K_SHORTEST_PATHS: {
|
||||
en = fromNodeKShortestPaths(en, member);
|
||||
break;
|
||||
}
|
||||
case NODE_TYPE_FILTER: {
|
||||
en = fromNodeFilter(en, member);
|
||||
break;
|
||||
|
|
|
@ -286,6 +286,9 @@ class ExecutionPlan {
|
|||
/// @brief create an execution plan element from an AST SHORTEST PATH node
|
||||
ExecutionNode* fromNodeShortestPath(ExecutionNode*, AstNode const*);
|
||||
|
||||
/// @brief create an execution plan element from an AST K-SHORTEST PATHS node
|
||||
ExecutionNode* fromNodeKShortestPaths(ExecutionNode*, AstNode const*);
|
||||
|
||||
/// @brief create an execution plan element from an AST FILTER node
|
||||
ExecutionNode* fromNodeFilter(ExecutionNode*, AstNode const*);
|
||||
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2019 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 Markus Pfeiffer
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "KShortestPathsExecutor.h"
|
||||
#include "Aql/AqlValue.h"
|
||||
#include "Aql/OutputAqlItemRow.h"
|
||||
#include "Aql/Query.h"
|
||||
#include "Aql/SingleRowFetcher.h"
|
||||
#include "Aql/Stats.h"
|
||||
#include "Graph/KShortestPathsFinder.h"
|
||||
#include "Graph/ShortestPathOptions.h"
|
||||
#include "Graph/ShortestPathResult.h"
|
||||
#include "Transaction/Helpers.h"
|
||||
|
||||
#include <velocypack/Builder.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/StringRef.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::aql;
|
||||
using namespace arangodb::graph;
|
||||
|
||||
namespace {
|
||||
static bool isValidId(VPackSlice id) {
|
||||
TRI_ASSERT(id.isString());
|
||||
arangodb::velocypack::StringRef tester(id);
|
||||
return tester.find('/') != std::string::npos;
|
||||
}
|
||||
static RegisterId selectOutputRegister(
|
||||
std::shared_ptr<std::unordered_set<RegisterId> const> const& outputRegisters) {
|
||||
TRI_ASSERT(outputRegisters != nullptr);
|
||||
TRI_ASSERT(outputRegisters->size() == 1);
|
||||
RegisterId reg = *(outputRegisters->begin());
|
||||
TRI_ASSERT(reg != ExecutionNode::MaxRegisterId);
|
||||
return reg;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
KShortestPathsExecutorInfos::KShortestPathsExecutorInfos(
|
||||
std::shared_ptr<std::unordered_set<RegisterId>> inputRegisters,
|
||||
std::shared_ptr<std::unordered_set<RegisterId>> outputRegisters, RegisterId nrInputRegisters,
|
||||
RegisterId nrOutputRegisters, std::unordered_set<RegisterId> registersToClear,
|
||||
std::unordered_set<RegisterId> registersToKeep,
|
||||
std::unique_ptr<graph::KShortestPathsFinder>&& finder,
|
||||
InputVertex&& source, InputVertex&& target)
|
||||
: ExecutorInfos(std::move(inputRegisters), std::move(outputRegisters),
|
||||
nrInputRegisters, nrOutputRegisters,
|
||||
std::move(registersToClear), std::move(registersToKeep)),
|
||||
_finder(std::move(finder)),
|
||||
_source(std::move(source)),
|
||||
_target(std::move(target)),
|
||||
_outputRegister(::selectOutputRegister(_outRegs)) {}
|
||||
|
||||
KShortestPathsExecutorInfos::KShortestPathsExecutorInfos(KShortestPathsExecutorInfos&&) = default;
|
||||
KShortestPathsExecutorInfos::~KShortestPathsExecutorInfos() = default;
|
||||
|
||||
arangodb::graph::KShortestPathsFinder& KShortestPathsExecutorInfos::finder() const {
|
||||
TRI_ASSERT(_finder);
|
||||
return *_finder.get();
|
||||
}
|
||||
|
||||
bool KShortestPathsExecutorInfos::useRegisterForInput(bool isTarget) const {
|
||||
if (isTarget) {
|
||||
return _target.type == InputVertex::REGISTER;
|
||||
}
|
||||
return _source.type == InputVertex::REGISTER;
|
||||
}
|
||||
|
||||
RegisterId KShortestPathsExecutorInfos::getInputRegister(bool isTarget) const {
|
||||
TRI_ASSERT(useRegisterForInput(isTarget));
|
||||
if (isTarget) {
|
||||
return _target.reg;
|
||||
}
|
||||
return _source.reg;
|
||||
}
|
||||
|
||||
std::string const& KShortestPathsExecutorInfos::getInputValue(bool isTarget) const {
|
||||
TRI_ASSERT(!useRegisterForInput(isTarget));
|
||||
if (isTarget) {
|
||||
return _target.value;
|
||||
}
|
||||
return _source.value;
|
||||
}
|
||||
|
||||
RegisterId KShortestPathsExecutorInfos::getOutputRegister() const {
|
||||
TRI_ASSERT(_outputRegister != ExecutionNode::MaxRegisterId);
|
||||
return _outputRegister;
|
||||
}
|
||||
|
||||
graph::TraverserCache* KShortestPathsExecutorInfos::cache() const {
|
||||
return _finder->options().cache();
|
||||
}
|
||||
|
||||
KShortestPathsExecutor::KShortestPathsExecutor(Fetcher& fetcher, Infos& infos)
|
||||
: _infos(infos),
|
||||
_fetcher(fetcher),
|
||||
_input{CreateInvalidInputRowHint{}},
|
||||
_rowState(ExecutionState::HASMORE),
|
||||
_finder{infos.finder()},
|
||||
_sourceBuilder{},
|
||||
_targetBuilder{} {
|
||||
if (!_infos.useRegisterForInput(false)) {
|
||||
_sourceBuilder.add(VPackValue(_infos.getInputValue(false)));
|
||||
}
|
||||
if (!_infos.useRegisterForInput(true)) {
|
||||
_targetBuilder.add(VPackValue(_infos.getInputValue(true)));
|
||||
}
|
||||
}
|
||||
|
||||
KShortestPathsExecutor::~KShortestPathsExecutor() = default;
|
||||
|
||||
// Shutdown query
|
||||
std::pair<ExecutionState, Result> KShortestPathsExecutor::shutdown(int errorCode) {
|
||||
_finder.destroyEngines();
|
||||
return {ExecutionState::DONE, TRI_ERROR_NO_ERROR};
|
||||
}
|
||||
|
||||
std::pair<ExecutionState, NoStats> KShortestPathsExecutor::produceRow(OutputAqlItemRow& output) {
|
||||
NoStats s;
|
||||
|
||||
while (true) {
|
||||
// We will have paths available, or return
|
||||
if (!_finder.isPathAvailable()) {
|
||||
if (!fetchPaths()) {
|
||||
return {_rowState, s};
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have a path available, so we go and output it
|
||||
transaction::BuilderLeaser tmp(_finder.options().trx());
|
||||
tmp->clear();
|
||||
if (_finder.getNextPathAql(*tmp.builder())) {
|
||||
AqlValue path = AqlValue(*tmp.builder());
|
||||
AqlValueGuard guard{path, true};
|
||||
output.moveValueInto(_infos.getOutputRegister(), _input, guard);
|
||||
return {computeState(), s};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool KShortestPathsExecutor::fetchPaths() {
|
||||
VPackSlice start;
|
||||
VPackSlice end;
|
||||
do {
|
||||
// Fetch a row from upstream
|
||||
std::tie(_rowState, _input) = _fetcher.fetchRow();
|
||||
if (!_input.isInitialized()) {
|
||||
// Either WAITING or DONE, in either case we cannot produce any paths.
|
||||
TRI_ASSERT(_rowState == ExecutionState::WAITING || _rowState == ExecutionState::DONE);
|
||||
return false;
|
||||
}
|
||||
// Check start and end for validity
|
||||
if (!getVertexId(false, start) || !getVertexId(true, end)) {
|
||||
// Fetch another row
|
||||
continue;
|
||||
}
|
||||
TRI_ASSERT(start.isString());
|
||||
TRI_ASSERT(end.isString());
|
||||
} while (!_finder.startKShortestPathsTraversal(start, end));
|
||||
return true;
|
||||
}
|
||||
|
||||
ExecutionState KShortestPathsExecutor::computeState() const {
|
||||
if (_rowState == ExecutionState::HASMORE || _finder.isPathAvailable()) {
|
||||
return ExecutionState::HASMORE;
|
||||
}
|
||||
return ExecutionState::DONE;
|
||||
}
|
||||
|
||||
bool KShortestPathsExecutor::getVertexId(bool isTarget, VPackSlice& id) {
|
||||
if (_infos.useRegisterForInput(isTarget)) {
|
||||
// The input row stays valid until the next fetchRow is executed.
|
||||
// So the slice can easily point to it.
|
||||
RegisterId reg = _infos.getInputRegister(isTarget);
|
||||
AqlValue const& in = _input.getValue(reg);
|
||||
if (in.isObject()) {
|
||||
try {
|
||||
auto idString = _finder.options().trx()->extractIdString(in.slice());
|
||||
if (isTarget) {
|
||||
_targetBuilder.clear();
|
||||
_targetBuilder.add(VPackValue(idString));
|
||||
id = _targetBuilder.slice();
|
||||
} else {
|
||||
_sourceBuilder.clear();
|
||||
_sourceBuilder.add(VPackValue(idString));
|
||||
id = _sourceBuilder.slice();
|
||||
}
|
||||
// Guranteed by extractIdValue
|
||||
TRI_ASSERT(::isValidId(id));
|
||||
} catch (...) {
|
||||
// _id or _key not present... ignore this error and fall through
|
||||
// returning no path
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (in.isString()) {
|
||||
id = in.slice();
|
||||
// Validation
|
||||
if (!::isValidId(id)) {
|
||||
_finder.options().query()->registerWarning(
|
||||
TRI_ERROR_BAD_PARAMETER,
|
||||
"Invalid input for k Shortest Paths: "
|
||||
"Only id strings or objects with "
|
||||
"_id are allowed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
_finder.options().query()->registerWarning(
|
||||
TRI_ERROR_BAD_PARAMETER,
|
||||
"Invalid input for k Shortest Paths: "
|
||||
"Only id strings or objects with "
|
||||
"_id are allowed");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (isTarget) {
|
||||
id = _targetBuilder.slice();
|
||||
} else {
|
||||
id = _sourceBuilder.slice();
|
||||
}
|
||||
if (!::isValidId(id)) {
|
||||
_finder.options().query()->registerWarning(
|
||||
TRI_ERROR_BAD_PARAMETER,
|
||||
"Invalid input for k Shortest Paths: "
|
||||
"Only id strings or objects with "
|
||||
"_id are allowed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2019 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 Markus Pfeiffer
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGOD_AQL_KSHORTEST_PATHS_EXECUTOR_H
|
||||
#define ARANGOD_AQL_KSHORTEST_PATHS_EXECUTOR_H
|
||||
|
||||
#include "Aql/ExecutionState.h"
|
||||
#include "Aql/ExecutorInfos.h"
|
||||
#include "Aql/InputAqlItemRow.h"
|
||||
|
||||
#include <velocypack/Builder.h>
|
||||
|
||||
namespace arangodb {
|
||||
|
||||
class Result;
|
||||
|
||||
namespace velocypack {
|
||||
class Slice;
|
||||
}
|
||||
|
||||
namespace graph {
|
||||
class KShortestPathsFinder;
|
||||
class ShortestPathFinder;
|
||||
class ShortestPathResult;
|
||||
class TraverserCache;
|
||||
} // namespace graph
|
||||
|
||||
namespace aql {
|
||||
|
||||
template <bool>
|
||||
class SingleRowFetcher;
|
||||
class OutputAqlItemRow;
|
||||
class NoStats;
|
||||
|
||||
class KShortestPathsExecutorInfos : public ExecutorInfos {
|
||||
public:
|
||||
struct InputVertex {
|
||||
enum { CONSTANT, REGISTER } type;
|
||||
// TODO make the following two a union instead
|
||||
RegisterId reg;
|
||||
std::string value;
|
||||
|
||||
// cppcheck-suppress passedByValue
|
||||
explicit InputVertex(std::string value)
|
||||
: type(CONSTANT), reg(0), value(std::move(value)) {}
|
||||
explicit InputVertex(RegisterId reg)
|
||||
: type(REGISTER), reg(reg), value("") {}
|
||||
};
|
||||
|
||||
KShortestPathsExecutorInfos(std::shared_ptr<std::unordered_set<RegisterId>> inputRegisters,
|
||||
std::shared_ptr<std::unordered_set<RegisterId>> outputRegisters,
|
||||
RegisterId nrInputRegisters, RegisterId nrOutputRegisters,
|
||||
std::unordered_set<RegisterId> registersToClear,
|
||||
std::unordered_set<RegisterId> registersToKeep,
|
||||
std::unique_ptr<graph::KShortestPathsFinder>&& finder,
|
||||
InputVertex&& source, InputVertex&& target);
|
||||
|
||||
KShortestPathsExecutorInfos() = delete;
|
||||
|
||||
KShortestPathsExecutorInfos(KShortestPathsExecutorInfos&&);
|
||||
KShortestPathsExecutorInfos(KShortestPathsExecutorInfos const&) = delete;
|
||||
~KShortestPathsExecutorInfos();
|
||||
|
||||
arangodb::graph::KShortestPathsFinder& finder() const;
|
||||
|
||||
/**
|
||||
* @brief test if we use a register or a constant input
|
||||
*
|
||||
* @param isTarget defines if we look for target(true) or source(false)
|
||||
*/
|
||||
bool useRegisterForInput(bool isTarget) const;
|
||||
|
||||
/**
|
||||
* @brief get the register used for the input
|
||||
*
|
||||
* @param isTarget defines if we look for target(true) or source(false)
|
||||
*/
|
||||
RegisterId getInputRegister(bool isTarget) const;
|
||||
|
||||
/**
|
||||
* @brief get the const value for the input
|
||||
*
|
||||
* @param isTarget defines if we look for target(true) or source(false)
|
||||
*/
|
||||
std::string const& getInputValue(bool isTarget) const;
|
||||
|
||||
/**
|
||||
* @brief get the output register for the given type
|
||||
*/
|
||||
RegisterId getOutputRegister() const;
|
||||
|
||||
graph::TraverserCache* cache() const;
|
||||
|
||||
private:
|
||||
/// @brief the shortest path finder.
|
||||
std::unique_ptr<arangodb::graph::KShortestPathsFinder> _finder;
|
||||
|
||||
/// @brief Information about the source vertex
|
||||
InputVertex const _source;
|
||||
|
||||
/// @brief Information about the target vertex
|
||||
InputVertex const _target;
|
||||
|
||||
RegisterId const _outputRegister;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementation of ShortestPath Node
|
||||
*/
|
||||
class KShortestPathsExecutor {
|
||||
public:
|
||||
struct Properties {
|
||||
static const bool preservesOrder = true;
|
||||
static const bool allowsBlockPassthrough = false;
|
||||
static const bool inputSizeRestrictsOutputSize = false;
|
||||
};
|
||||
using Fetcher = SingleRowFetcher<Properties::allowsBlockPassthrough>;
|
||||
using Infos = KShortestPathsExecutorInfos;
|
||||
using Stats = NoStats;
|
||||
|
||||
KShortestPathsExecutor() = delete;
|
||||
KShortestPathsExecutor(KShortestPathsExecutor&&) = default;
|
||||
|
||||
KShortestPathsExecutor(Fetcher& fetcher, Infos&);
|
||||
~KShortestPathsExecutor();
|
||||
|
||||
/**
|
||||
* @brief Shutdown will be called once for every query
|
||||
*
|
||||
* @return ExecutionState and no error.
|
||||
*/
|
||||
std::pair<ExecutionState, Result> shutdown(int errorCode);
|
||||
|
||||
/**
|
||||
* @brief produce the next Row of Aql Values.
|
||||
*
|
||||
* @return ExecutionState, and if successful exactly one new Row of AqlItems.
|
||||
*/
|
||||
std::pair<ExecutionState, Stats> produceRow(OutputAqlItemRow& output);
|
||||
inline std::pair<ExecutionState, size_t> expectedNumberOfRows(size_t) const {
|
||||
TRI_ASSERT(false);
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(
|
||||
TRI_ERROR_INTERNAL,
|
||||
"Logic_error, prefetching number fo rows not supported");
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Fetch input row(s) and compute path
|
||||
*
|
||||
* @return false if we are done and no path could be found.
|
||||
*/
|
||||
bool fetchPaths();
|
||||
|
||||
/**
|
||||
* @brief compute the correct return state
|
||||
*
|
||||
* @return DONE if no more is expected
|
||||
*/
|
||||
|
||||
ExecutionState computeState() const;
|
||||
|
||||
/**
|
||||
* @brief get the id of a input vertex
|
||||
*/
|
||||
bool getVertexId(bool isTarget, arangodb::velocypack::Slice& id);
|
||||
|
||||
private:
|
||||
Infos& _infos;
|
||||
Fetcher& _fetcher;
|
||||
InputAqlItemRow _input;
|
||||
ExecutionState _rowState;
|
||||
/// @brief the shortest path finder.
|
||||
arangodb::graph::KShortestPathsFinder& _finder;
|
||||
|
||||
/// @brief temporary memory mangement for source id
|
||||
arangodb::velocypack::Builder _sourceBuilder;
|
||||
/// @brief temporary memory mangement for target id
|
||||
arangodb::velocypack::Builder _targetBuilder;
|
||||
};
|
||||
} // namespace aql
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -0,0 +1,359 @@
|
|||
/// @brief Implementation of k Shortest Path Execution Node
|
||||
///
|
||||
/// @file arangod/Aql/KShortestPathsNode.cpp
|
||||
///
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2010-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
|
||||
/// @author Markus Pfeiffer
|
||||
/// @author Copyright 2015, ArangoDB GmbH, Cologne, Germany
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "KShortestPathsNode.h"
|
||||
#include "Aql/Ast.h"
|
||||
#include "Aql/Collection.h"
|
||||
#include "Aql/ExecutionBlockImpl.h"
|
||||
#include "Aql/ExecutionPlan.h"
|
||||
#include "Aql/KShortestPathsExecutor.h"
|
||||
#include "Aql/Query.h"
|
||||
#include "Graph/AttributeWeightShortestPathFinder.h"
|
||||
#include "Graph/KShortestPathsFinder.h"
|
||||
#include "Graph/ShortestPathFinder.h"
|
||||
#include "Graph/ShortestPathOptions.h"
|
||||
#include "Graph/ShortestPathResult.h"
|
||||
#include "Indexes/Index.h"
|
||||
#include "Utils/CollectionNameResolver.h"
|
||||
#include "VocBase/LogicalCollection.h"
|
||||
|
||||
#include <velocypack/Iterator.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::basics;
|
||||
using namespace arangodb::aql;
|
||||
using namespace arangodb::graph;
|
||||
|
||||
namespace {
|
||||
static void parseNodeInput(AstNode const* node, std::string& id, Variable const*& variable) {
|
||||
switch (node->type) {
|
||||
case NODE_TYPE_REFERENCE:
|
||||
variable = static_cast<Variable*>(node->getData());
|
||||
id = "";
|
||||
break;
|
||||
case NODE_TYPE_VALUE:
|
||||
if (node->value.type != VALUE_TYPE_STRING) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_PARSE,
|
||||
"invalid start vertex. Must either be "
|
||||
"an _id string or an object with _id.");
|
||||
}
|
||||
variable = nullptr;
|
||||
id = node->getString();
|
||||
break;
|
||||
default:
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_PARSE,
|
||||
"invalid start vertex. Must either be an "
|
||||
"_id string or an object with _id.");
|
||||
}
|
||||
}
|
||||
static KShortestPathsExecutorInfos::InputVertex prepareVertexInput(KShortestPathsNode const* node,
|
||||
bool isTarget) {
|
||||
using InputVertex = KShortestPathsExecutorInfos::InputVertex;
|
||||
if (isTarget) {
|
||||
if (node->usesTargetInVariable()) {
|
||||
auto it = node->getRegisterPlan()->varInfo.find(node->targetInVariable().id);
|
||||
TRI_ASSERT(it != node->getRegisterPlan()->varInfo.end());
|
||||
return InputVertex{it->second.registerId};
|
||||
} else {
|
||||
return InputVertex{node->getTargetVertex()};
|
||||
}
|
||||
} else {
|
||||
if (node->usesStartInVariable()) {
|
||||
auto it = node->getRegisterPlan()->varInfo.find(node->startInVariable().id);
|
||||
TRI_ASSERT(it != node->getRegisterPlan()->varInfo.end());
|
||||
return InputVertex{it->second.registerId};
|
||||
} else {
|
||||
return InputVertex{node->getStartVertex()};
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
KShortestPathsNode::KShortestPathsNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase,
|
||||
AstNode const* direction, AstNode const* start,
|
||||
AstNode const* target, AstNode const* graph,
|
||||
std::unique_ptr<BaseOptions> options)
|
||||
: GraphNode(plan, id, vocbase, direction, graph, std::move(options)),
|
||||
_pathOutVariable(nullptr),
|
||||
_inStartVariable(nullptr),
|
||||
_inTargetVariable(nullptr),
|
||||
_fromCondition(nullptr),
|
||||
_toCondition(nullptr) {
|
||||
TRI_ASSERT(start != nullptr);
|
||||
TRI_ASSERT(target != nullptr);
|
||||
TRI_ASSERT(graph != nullptr);
|
||||
|
||||
auto ast = _plan->getAst();
|
||||
// Let us build the conditions on _from and _to. Just in case we need them.
|
||||
{
|
||||
auto const* access =
|
||||
ast->createNodeAttributeAccess(getTemporaryRefNode(),
|
||||
StaticStrings::FromString.c_str(),
|
||||
StaticStrings::FromString.length());
|
||||
auto const* cond =
|
||||
ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, access, _tmpIdNode);
|
||||
_fromCondition = ast->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND);
|
||||
_fromCondition->addMember(cond);
|
||||
}
|
||||
TRI_ASSERT(_fromCondition != nullptr);
|
||||
|
||||
{
|
||||
auto const* access =
|
||||
ast->createNodeAttributeAccess(getTemporaryRefNode(),
|
||||
StaticStrings::ToString.c_str(),
|
||||
StaticStrings::ToString.length());
|
||||
auto const* cond =
|
||||
ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, access, _tmpIdNode);
|
||||
_toCondition = ast->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND);
|
||||
_toCondition->addMember(cond);
|
||||
}
|
||||
TRI_ASSERT(_toCondition != nullptr);
|
||||
|
||||
parseNodeInput(start, _startVertexId, _inStartVariable);
|
||||
parseNodeInput(target, _targetVertexId, _inTargetVariable);
|
||||
}
|
||||
|
||||
/// @brief Internal constructor to clone the node.
|
||||
KShortestPathsNode::KShortestPathsNode(
|
||||
ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase,
|
||||
std::vector<std::unique_ptr<Collection>> const& edgeColls,
|
||||
std::vector<std::unique_ptr<Collection>> const& vertexColls,
|
||||
std::vector<TRI_edge_direction_e> const& directions, Variable const* inStartVariable,
|
||||
std::string const& startVertexId, Variable const* inTargetVariable,
|
||||
std::string const& targetVertexId, std::unique_ptr<BaseOptions> options)
|
||||
: GraphNode(plan, id, vocbase, edgeColls, vertexColls, directions, std::move(options)),
|
||||
_pathOutVariable(nullptr),
|
||||
_inStartVariable(inStartVariable),
|
||||
_startVertexId(startVertexId),
|
||||
_inTargetVariable(inTargetVariable),
|
||||
_targetVertexId(targetVertexId),
|
||||
_fromCondition(nullptr),
|
||||
_toCondition(nullptr) {}
|
||||
|
||||
KShortestPathsNode::~KShortestPathsNode() {}
|
||||
|
||||
KShortestPathsNode::KShortestPathsNode(ExecutionPlan* plan,
|
||||
arangodb::velocypack::Slice const& base)
|
||||
: GraphNode(plan, base),
|
||||
_pathOutVariable(nullptr),
|
||||
_inStartVariable(nullptr),
|
||||
_inTargetVariable(nullptr),
|
||||
_fromCondition(nullptr),
|
||||
_toCondition(nullptr) {
|
||||
// Path out variable
|
||||
if (base.hasKey("pathOutVariable")) {
|
||||
_pathOutVariable =
|
||||
Variable::varFromVPack(plan->getAst(), base, "pathOutVariable");
|
||||
}
|
||||
|
||||
// Start Vertex
|
||||
if (base.hasKey("startInVariable")) {
|
||||
_inStartVariable =
|
||||
Variable::varFromVPack(plan->getAst(), base, "startInVariable");
|
||||
} else {
|
||||
VPackSlice v = base.get("startVertexId");
|
||||
if (!v.isString()) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN,
|
||||
"start vertex must be a string");
|
||||
}
|
||||
_startVertexId = v.copyString();
|
||||
|
||||
if (_startVertexId.empty()) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN,
|
||||
"start vertex mustn't be empty");
|
||||
}
|
||||
}
|
||||
|
||||
// Target Vertex
|
||||
if (base.hasKey("targetInVariable")) {
|
||||
_inTargetVariable =
|
||||
Variable::varFromVPack(plan->getAst(), base, "targetInVariable");
|
||||
} else {
|
||||
VPackSlice v = base.get("targetVertexId");
|
||||
if (!v.isString()) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN,
|
||||
"target vertex must be a string");
|
||||
}
|
||||
_targetVertexId = v.copyString();
|
||||
if (_targetVertexId.empty()) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_BAD_JSON_PLAN,
|
||||
"target vertex mustn't be empty");
|
||||
}
|
||||
}
|
||||
|
||||
// Filter Condition Parts
|
||||
TRI_ASSERT(base.hasKey("fromCondition"));
|
||||
_fromCondition = new AstNode(plan->getAst(), base.get("fromCondition"));
|
||||
|
||||
TRI_ASSERT(base.hasKey("toCondition"));
|
||||
_toCondition = new AstNode(plan->getAst(), base.get("toCondition"));
|
||||
}
|
||||
|
||||
void KShortestPathsNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) const {
|
||||
GraphNode::toVelocyPackHelper(nodes, flags); // call base class method
|
||||
// Out variables
|
||||
if (usesPathOutVariable()) {
|
||||
nodes.add(VPackValue("pathOutVariable"));
|
||||
pathOutVariable().toVelocyPack(nodes);
|
||||
}
|
||||
|
||||
// In variables
|
||||
if (usesStartInVariable()) {
|
||||
nodes.add(VPackValue("startInVariable"));
|
||||
startInVariable().toVelocyPack(nodes);
|
||||
} else {
|
||||
nodes.add("startVertexId", VPackValue(_startVertexId));
|
||||
}
|
||||
|
||||
if (usesTargetInVariable()) {
|
||||
nodes.add(VPackValue("targetInVariable"));
|
||||
targetInVariable().toVelocyPack(nodes);
|
||||
} else {
|
||||
nodes.add("targetVertexId", VPackValue(_targetVertexId));
|
||||
}
|
||||
|
||||
// Filter Conditions
|
||||
TRI_ASSERT(_fromCondition != nullptr);
|
||||
nodes.add(VPackValue("fromCondition"));
|
||||
_fromCondition->toVelocyPack(nodes, flags);
|
||||
|
||||
TRI_ASSERT(_toCondition != nullptr);
|
||||
nodes.add(VPackValue("toCondition"));
|
||||
_toCondition->toVelocyPack(nodes, flags);
|
||||
|
||||
// And close it:
|
||||
nodes.close();
|
||||
}
|
||||
|
||||
/// @brief creates corresponding ExecutionBlock
|
||||
std::unique_ptr<ExecutionBlock> KShortestPathsNode::createBlock(
|
||||
ExecutionEngine& engine, std::unordered_map<ExecutionNode*, ExecutionBlock*> const&) const {
|
||||
ExecutionNode const* previousNode = getFirstDependency();
|
||||
TRI_ASSERT(previousNode != nullptr);
|
||||
auto inputRegisters = std::make_shared<std::unordered_set<RegisterId>>();
|
||||
if (usesStartInVariable()) {
|
||||
inputRegisters->emplace(varToRegUnchecked(startInVariable()));
|
||||
}
|
||||
if (usesTargetInVariable()) {
|
||||
inputRegisters->emplace(varToRegUnchecked(targetInVariable()));
|
||||
}
|
||||
|
||||
auto outputRegisters = std::make_shared<std::unordered_set<RegisterId>>();
|
||||
TRI_ASSERT(usesPathOutVariable()); // This node always produces the path!
|
||||
outputRegisters->emplace(varToRegUnchecked(pathOutVariable()));
|
||||
|
||||
auto opts = static_cast<ShortestPathOptions*>(options());
|
||||
|
||||
KShortestPathsExecutorInfos::InputVertex sourceInput = ::prepareVertexInput(this, false);
|
||||
KShortestPathsExecutorInfos::InputVertex targetInput = ::prepareVertexInput(this, true);
|
||||
|
||||
std::unique_ptr<KShortestPathsFinder> finder;
|
||||
finder.reset(new graph::KShortestPathsFinder(*opts));
|
||||
|
||||
TRI_ASSERT(finder != nullptr);
|
||||
KShortestPathsExecutorInfos infos(inputRegisters, outputRegisters,
|
||||
getRegisterPlan()->nrRegs[previousNode->getDepth()],
|
||||
getRegisterPlan()->nrRegs[getDepth()],
|
||||
getRegsToClear(), calcRegsToKeep(), std::move(finder),
|
||||
std::move(sourceInput), std::move(targetInput));
|
||||
return std::make_unique<ExecutionBlockImpl<KShortestPathsExecutor>>(&engine, this,
|
||||
std::move(infos));
|
||||
}
|
||||
|
||||
ExecutionNode* KShortestPathsNode::clone(ExecutionPlan* plan, bool withDependencies,
|
||||
bool withProperties) const {
|
||||
TRI_ASSERT(!_optionsBuilt);
|
||||
auto oldOpts = static_cast<ShortestPathOptions*>(options());
|
||||
std::unique_ptr<BaseOptions> tmp = std::make_unique<ShortestPathOptions>(*oldOpts);
|
||||
auto c = std::make_unique<KShortestPathsNode>(plan, _id, _vocbase, _edgeColls,
|
||||
_vertexColls, _directions, _inStartVariable,
|
||||
_startVertexId, _inTargetVariable,
|
||||
_targetVertexId, std::move(tmp));
|
||||
if (usesPathOutVariable()) {
|
||||
auto pathOutVariable = _pathOutVariable;
|
||||
if (withProperties) {
|
||||
pathOutVariable = plan->getAst()->variables()->createVariable(pathOutVariable);
|
||||
}
|
||||
TRI_ASSERT(pathOutVariable != nullptr);
|
||||
c->setPathOutput(pathOutVariable);
|
||||
}
|
||||
|
||||
// Temporary Filter Objects
|
||||
c->_tmpObjVariable = _tmpObjVariable;
|
||||
c->_tmpObjVarNode = _tmpObjVarNode;
|
||||
c->_tmpIdNode = _tmpIdNode;
|
||||
|
||||
// Filter Condition Parts
|
||||
c->_fromCondition = _fromCondition->clone(_plan->getAst());
|
||||
c->_toCondition = _toCondition->clone(_plan->getAst());
|
||||
|
||||
return cloneHelper(std::move(c), withDependencies, withProperties);
|
||||
}
|
||||
|
||||
void KShortestPathsNode::prepareOptions() {
|
||||
if (_optionsBuilt) {
|
||||
return;
|
||||
}
|
||||
TRI_ASSERT(!_optionsBuilt);
|
||||
|
||||
size_t numEdgeColls = _edgeColls.size();
|
||||
Ast* ast = _plan->getAst();
|
||||
auto opts = static_cast<ShortestPathOptions*>(options());
|
||||
|
||||
opts->setVariable(getTemporaryVariable());
|
||||
|
||||
// Compute Indexes.
|
||||
for (size_t i = 0; i < numEdgeColls; ++i) {
|
||||
auto dir = _directions[i];
|
||||
switch (dir) {
|
||||
case TRI_EDGE_IN:
|
||||
opts->addLookupInfo(_plan, _edgeColls[i]->name(),
|
||||
StaticStrings::ToString, _toCondition->clone(ast));
|
||||
opts->addReverseLookupInfo(_plan, _edgeColls[i]->name(), StaticStrings::FromString,
|
||||
_fromCondition->clone(ast));
|
||||
break;
|
||||
case TRI_EDGE_OUT:
|
||||
opts->addLookupInfo(_plan, _edgeColls[i]->name(),
|
||||
StaticStrings::FromString, _fromCondition->clone(ast));
|
||||
opts->addReverseLookupInfo(_plan, _edgeColls[i]->name(), StaticStrings::ToString,
|
||||
_toCondition->clone(ast));
|
||||
break;
|
||||
case TRI_EDGE_ANY:
|
||||
TRI_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If we use the path output the cache should activate document
|
||||
// caching otherwise it is not worth it.
|
||||
if (ServerState::instance()->isCoordinator()) {
|
||||
_options->activateCache(false, engines());
|
||||
} else {
|
||||
_options->activateCache(false, nullptr);
|
||||
}
|
||||
_optionsBuilt = true;
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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
|
||||
/// @author Markus Pfeiffer
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGOD_AQL_KSHORTEST_PATHS_NODE_H
|
||||
#define ARANGOD_AQL_KSHORTEST_PATHS_NODE_H 1
|
||||
|
||||
#include "Aql/GraphNode.h"
|
||||
#include "Aql/Graphs.h"
|
||||
|
||||
namespace arangodb {
|
||||
|
||||
namespace velocypack {
|
||||
class Builder;
|
||||
}
|
||||
|
||||
namespace graph {
|
||||
struct BaseOptions;
|
||||
struct ShortestPathOptions;
|
||||
} // namespace graph
|
||||
namespace aql {
|
||||
|
||||
/// @brief class KShortestPathsNode
|
||||
class KShortestPathsNode : public GraphNode {
|
||||
friend class ExecutionBlock;
|
||||
friend class RedundantCalculationsReplacer;
|
||||
|
||||
/// @brief constructor with a vocbase and a collection name
|
||||
public:
|
||||
KShortestPathsNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase,
|
||||
AstNode const* direction, AstNode const* start,
|
||||
AstNode const* target, AstNode const* graph,
|
||||
std::unique_ptr<graph::BaseOptions> options);
|
||||
|
||||
KShortestPathsNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base);
|
||||
|
||||
~KShortestPathsNode();
|
||||
|
||||
/// @brief Internal constructor to clone the node.
|
||||
KShortestPathsNode(ExecutionPlan* plan, size_t id, TRI_vocbase_t* vocbase,
|
||||
std::vector<std::unique_ptr<Collection>> const& edgeColls,
|
||||
std::vector<std::unique_ptr<Collection>> const& vertexColls,
|
||||
std::vector<TRI_edge_direction_e> const& directions,
|
||||
Variable const* inStartVariable, std::string const& startVertexId,
|
||||
Variable const* inTargetVariable, std::string const& targetVertexId,
|
||||
std::unique_ptr<graph::BaseOptions> options);
|
||||
|
||||
public:
|
||||
/// @brief return the type of the node
|
||||
NodeType getType() const override final { return K_SHORTEST_PATHS; }
|
||||
|
||||
/// @brief export to VelocyPack
|
||||
void toVelocyPackHelper(arangodb::velocypack::Builder&, unsigned flags) const override final;
|
||||
|
||||
/// @brief creates corresponding ExecutionBlock
|
||||
std::unique_ptr<ExecutionBlock> createBlock(
|
||||
ExecutionEngine& engine,
|
||||
std::unordered_map<ExecutionNode*, ExecutionBlock*> const&) const override;
|
||||
|
||||
/// @brief clone ExecutionNode recursively
|
||||
ExecutionNode* clone(ExecutionPlan* plan, bool withDependencies,
|
||||
bool withProperties) const override final;
|
||||
|
||||
bool usesPathOutVariable() const { return _pathOutVariable != nullptr; }
|
||||
Variable const& pathOutVariable() const {
|
||||
TRI_ASSERT(_pathOutVariable != nullptr);
|
||||
return *_pathOutVariable;
|
||||
}
|
||||
|
||||
void setPathOutput(Variable const* outVar) { _pathOutVariable = outVar; }
|
||||
|
||||
/// @brief Test if this node uses an in variable or constant for start
|
||||
bool usesStartInVariable() const { return _inStartVariable != nullptr; }
|
||||
|
||||
/// @brief return the start variable
|
||||
Variable const& startInVariable() const {
|
||||
TRI_ASSERT(_inStartVariable != nullptr);
|
||||
return *_inStartVariable;
|
||||
}
|
||||
|
||||
std::string const getStartVertex() const { return _startVertexId; }
|
||||
|
||||
/// @brief Test if this node uses an in variable or constant for target
|
||||
bool usesTargetInVariable() const { return _inTargetVariable != nullptr; }
|
||||
|
||||
/// @brief return the target variable
|
||||
Variable const& targetInVariable() const {
|
||||
TRI_ASSERT(_inTargetVariable != nullptr);
|
||||
return *_inTargetVariable;
|
||||
}
|
||||
|
||||
std::string const getTargetVertex() const { return _targetVertexId; }
|
||||
|
||||
/// @brief getVariablesSetHere
|
||||
std::vector<Variable const*> getVariablesSetHere() const override final {
|
||||
std::vector<Variable const*> vars;
|
||||
TRI_ASSERT(_pathOutVariable != nullptr);
|
||||
vars.emplace_back(_pathOutVariable);
|
||||
return vars;
|
||||
}
|
||||
|
||||
/// @brief getVariablesUsedHere, modifying the set in-place
|
||||
void getVariablesUsedHere(arangodb::HashSet<Variable const*>& vars) const override {
|
||||
if (_inStartVariable != nullptr) {
|
||||
vars.emplace(_inStartVariable);
|
||||
}
|
||||
if (_inTargetVariable != nullptr) {
|
||||
vars.emplace(_inTargetVariable);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Compute the shortest path options containing the expressions
|
||||
/// MUST! be called after optimization and before creation
|
||||
/// of blocks.
|
||||
void prepareOptions() override;
|
||||
|
||||
private:
|
||||
/// @brief path output variable
|
||||
Variable const* _pathOutVariable;
|
||||
|
||||
/// @brief input variable only used if _vertexId is unused
|
||||
Variable const* _inStartVariable;
|
||||
|
||||
/// @brief input vertexId only used if _inVariable is unused
|
||||
std::string _startVertexId;
|
||||
|
||||
/// @brief input variable only used if _vertexId is unused
|
||||
Variable const* _inTargetVariable;
|
||||
|
||||
/// @brief input vertexId only used if _inVariable is unused
|
||||
std::string _targetVertexId;
|
||||
|
||||
/// @brief The hard coded condition on _from
|
||||
AstNode* _fromCondition;
|
||||
|
||||
/// @brief The hard coded condition on _to
|
||||
AstNode* _toCondition;
|
||||
};
|
||||
|
||||
} // namespace aql
|
||||
} // namespace arangodb
|
||||
|
||||
#endif
|
|
@ -36,6 +36,7 @@
|
|||
#include "Aql/Function.h"
|
||||
#include "Aql/IResearchViewNode.h"
|
||||
#include "Aql/IndexNode.h"
|
||||
#include "Aql/KShortestPathsNode.h"
|
||||
#include "Aql/ModificationNodes.h"
|
||||
#include "Aql/Optimizer.h"
|
||||
#include "Aql/Query.h"
|
||||
|
@ -82,7 +83,9 @@ bool accessesCollectionVariable(arangodb::aql::ExecutionPlan const* plan,
|
|||
}
|
||||
if (setter->getType() == EN::INDEX || setter->getType() == EN::ENUMERATE_COLLECTION ||
|
||||
setter->getType() == EN::ENUMERATE_IRESEARCH_VIEW ||
|
||||
setter->getType() == EN::SUBQUERY || setter->getType() == EN::TRAVERSAL ||
|
||||
setter->getType() == EN::SUBQUERY ||
|
||||
setter->getType() == EN::TRAVERSAL ||
|
||||
setter->getType() == EN::K_SHORTEST_PATHS ||
|
||||
setter->getType() == EN::SHORTEST_PATH) {
|
||||
return true;
|
||||
}
|
||||
|
@ -107,8 +110,10 @@ arangodb::aql::Collection const* getCollection(arangodb::aql::ExecutionNode cons
|
|||
case EN::INDEX:
|
||||
return ExecutionNode::castTo<arangodb::aql::IndexNode const*>(node)->collection();
|
||||
case EN::TRAVERSAL:
|
||||
case EN::K_SHORTEST_PATHS:
|
||||
case EN::SHORTEST_PATH:
|
||||
return ExecutionNode::castTo<arangodb::aql::GraphNode const*>(node)->collection();
|
||||
|
||||
default:
|
||||
// note: modification nodes are not covered here yet
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
|
||||
|
@ -345,6 +350,7 @@ class RestrictToSingleShardChecker final
|
|||
|
||||
switch (en->getType()) {
|
||||
case EN::TRAVERSAL:
|
||||
case EN::K_SHORTEST_PATHS:
|
||||
case EN::SHORTEST_PATH: {
|
||||
_stop = true;
|
||||
return true; // abort enumerating, we are done already!
|
||||
|
@ -1048,6 +1054,7 @@ void arangodb::aql::removeRedundantSortsRule(Optimizer* opt,
|
|||
} else if (current->getType() == EN::ENUMERATE_LIST ||
|
||||
current->getType() == EN::ENUMERATE_COLLECTION ||
|
||||
current->getType() == EN::TRAVERSAL ||
|
||||
current->getType() == EN::K_SHORTEST_PATHS ||
|
||||
current->getType() == EN::SHORTEST_PATH) {
|
||||
// ok, but we cannot remove two different sorts if one of these node
|
||||
// types is between them
|
||||
|
@ -1664,7 +1671,9 @@ void arangodb::aql::moveCalculationsDownRule(Optimizer* opt,
|
|||
} else if (currentType == EN::INDEX || currentType == EN::ENUMERATE_COLLECTION ||
|
||||
currentType == EN::ENUMERATE_IRESEARCH_VIEW ||
|
||||
currentType == EN::ENUMERATE_LIST ||
|
||||
currentType == EN::TRAVERSAL || currentType == EN::SHORTEST_PATH ||
|
||||
currentType == EN::TRAVERSAL ||
|
||||
currentType == EN::SHORTEST_PATH ||
|
||||
currentType == EN::K_SHORTEST_PATHS ||
|
||||
currentType == EN::COLLECT || currentType == EN::NORESULTS) {
|
||||
// we will not push further down than such nodes
|
||||
done = true;
|
||||
|
@ -2084,6 +2093,11 @@ class arangodb::aql::RedundantCalculationsReplacer final
|
|||
break;
|
||||
}
|
||||
|
||||
case EN::K_SHORTEST_PATHS: {
|
||||
replaceStartTargetVariables<ShortestPathNode>(en);
|
||||
break;
|
||||
}
|
||||
|
||||
case EN::SHORTEST_PATH: {
|
||||
replaceStartTargetVariables<ShortestPathNode>(en);
|
||||
break;
|
||||
|
@ -3118,6 +3132,7 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
|||
bool before(ExecutionNode* en) override final {
|
||||
switch (en->getType()) {
|
||||
case EN::TRAVERSAL:
|
||||
case EN::K_SHORTEST_PATHS:
|
||||
case EN::SHORTEST_PATH:
|
||||
case EN::ENUMERATE_LIST:
|
||||
case EN::ENUMERATE_IRESEARCH_VIEW:
|
||||
|
@ -3507,6 +3522,7 @@ void arangodb::aql::optimizeClusterSingleShardRule(Optimizer* opt,
|
|||
SmallVector<ExecutionNode*>::allocator_type::arena_type s;
|
||||
SmallVector<ExecutionNode*> nodes{s};
|
||||
std::vector<ExecutionNode::NodeType> types = {ExecutionNode::TRAVERSAL,
|
||||
ExecutionNode::K_SHORTEST_PATHS,
|
||||
ExecutionNode::SHORTEST_PATH,
|
||||
ExecutionNode::SUBQUERY};
|
||||
plan->findNodesOfType(nodes, types, true);
|
||||
|
@ -4336,6 +4352,7 @@ void arangodb::aql::distributeFilternCalcToClusterRule(Optimizer* opt,
|
|||
case EN::INDEX:
|
||||
case EN::ENUMERATE_COLLECTION:
|
||||
case EN::TRAVERSAL:
|
||||
case EN::K_SHORTEST_PATHS:
|
||||
case EN::SHORTEST_PATH:
|
||||
case EN::SUBQUERY:
|
||||
case EN::ENUMERATE_IRESEARCH_VIEW:
|
||||
|
@ -4470,6 +4487,7 @@ void arangodb::aql::distributeSortToClusterRule(Optimizer* opt,
|
|||
case EN::LIMIT:
|
||||
case EN::INDEX:
|
||||
case EN::TRAVERSAL:
|
||||
case EN::K_SHORTEST_PATHS:
|
||||
case EN::SHORTEST_PATH:
|
||||
case EN::REMOTESINGLE:
|
||||
case EN::ENUMERATE_IRESEARCH_VIEW:
|
||||
|
@ -4999,6 +5017,7 @@ class RemoveToEnumCollFinder final : public WalkerWorker<ExecutionNode> {
|
|||
case EN::LIMIT:
|
||||
case EN::SORT:
|
||||
case EN::TRAVERSAL:
|
||||
case EN::K_SHORTEST_PATHS:
|
||||
case EN::SHORTEST_PATH: {
|
||||
// if we meet any of the above, then we abort . . .
|
||||
break;
|
||||
|
@ -5683,7 +5702,7 @@ void arangodb::aql::patchUpdateStatementsRule(Optimizer* opt,
|
|||
}
|
||||
modified = true;
|
||||
}
|
||||
} else if (type == EN::TRAVERSAL || type == EN::SHORTEST_PATH) {
|
||||
} else if (type == EN::TRAVERSAL || type == EN::K_SHORTEST_PATHS || type == EN::SHORTEST_PATH) {
|
||||
// unclear what will be read by the traversal
|
||||
modified = false;
|
||||
break;
|
||||
|
@ -5907,6 +5926,7 @@ void arangodb::aql::prepareTraversalsRule(Optimizer* opt,
|
|||
SmallVector<ExecutionNode*>::allocator_type::arena_type a;
|
||||
SmallVector<ExecutionNode*> tNodes{a};
|
||||
plan->findNodesOfType(tNodes, EN::TRAVERSAL, true);
|
||||
plan->findNodesOfType(tNodes, EN::K_SHORTEST_PATHS, true);
|
||||
plan->findNodesOfType(tNodes, EN::SHORTEST_PATH, true);
|
||||
|
||||
if (tNodes.empty()) {
|
||||
|
@ -5921,6 +5941,10 @@ void arangodb::aql::prepareTraversalsRule(Optimizer* opt,
|
|||
if (n->getType() == EN::TRAVERSAL) {
|
||||
TraversalNode* traversal = ExecutionNode::castTo<TraversalNode*>(n);
|
||||
traversal->prepareOptions();
|
||||
} else if(n->getType() == EN::K_SHORTEST_PATHS) {
|
||||
TRI_ASSERT(n->getType() == EN::K_SHORTEST_PATHS);
|
||||
KShortestPathsNode* spn = ExecutionNode::castTo<KShortestPathsNode*>(n);
|
||||
spn->prepareOptions();
|
||||
} else {
|
||||
TRI_ASSERT(n->getType() == EN::SHORTEST_PATH);
|
||||
ShortestPathNode* spn = ExecutionNode::castTo<ShortestPathNode*>(n);
|
||||
|
@ -6806,7 +6830,9 @@ void arangodb::aql::geoIndexRule(Optimizer* opt, std::unique_ptr<ExecutionPlan>
|
|||
current->getType() == EN::ENUMERATE_COLLECTION ||
|
||||
current->getType() == EN::ENUMERATE_LIST ||
|
||||
current->getType() == EN::ENUMERATE_IRESEARCH_VIEW ||
|
||||
current->getType() == EN::TRAVERSAL || current->getType() == EN::SHORTEST_PATH) {
|
||||
current->getType() == EN::TRAVERSAL ||
|
||||
current->getType() == EN::K_SHORTEST_PATHS ||
|
||||
current->getType() == EN::SHORTEST_PATH) {
|
||||
// invalidate limit and sort. filters can still be used
|
||||
limit = nullptr;
|
||||
info.sorted = false;
|
||||
|
@ -6846,7 +6872,9 @@ void arangodb::aql::sortLimitRule(Optimizer* opt, std::unique_ptr<ExecutionPlan>
|
|||
current->getType() == EN::ENUMERATE_COLLECTION ||
|
||||
current->getType() == EN::ENUMERATE_LIST ||
|
||||
current->getType() == EN::ENUMERATE_IRESEARCH_VIEW ||
|
||||
current->getType() == EN::TRAVERSAL || current->getType() == EN::SHORTEST_PATH ||
|
||||
current->getType() == EN::TRAVERSAL ||
|
||||
current->getType() == EN::SHORTEST_PATH ||
|
||||
current->getType() == EN::K_SHORTEST_PATHS ||
|
||||
current->getType() == EN::INDEX || current->getType() == EN::COLLECT) {
|
||||
// TODO check other end conditions
|
||||
break; // stop parsing
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,9 @@
|
|||
/* A Bison parser, made by GNU Bison 3.0.4. */
|
||||
/* A Bison parser, made by GNU Bison 3.3.2. */
|
||||
|
||||
/* Bison interface for Yacc-like parsers in C
|
||||
|
||||
Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
|
||||
Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2019 Free Software Foundation,
|
||||
Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -30,6 +31,9 @@
|
|||
This special exception was added by the Free Software Foundation in
|
||||
version 2.2 of Bison. */
|
||||
|
||||
/* Undocumented macros, especially those whose name start with YY_,
|
||||
are private implementation details. Do not rely on them. */
|
||||
|
||||
#ifndef YY_AQL_AQL_GRAMMAR_HPP_INCLUDED
|
||||
# define YY_AQL_AQL_GRAMMAR_HPP_INCLUDED
|
||||
/* Debug traces. */
|
||||
|
@ -61,62 +65,63 @@ extern int Aqldebug;
|
|||
T_AGGREGATE = 270,
|
||||
T_GRAPH = 271,
|
||||
T_SHORTEST_PATH = 272,
|
||||
T_DISTINCT = 273,
|
||||
T_REMOVE = 274,
|
||||
T_INSERT = 275,
|
||||
T_UPDATE = 276,
|
||||
T_REPLACE = 277,
|
||||
T_UPSERT = 278,
|
||||
T_NULL = 279,
|
||||
T_TRUE = 280,
|
||||
T_FALSE = 281,
|
||||
T_STRING = 282,
|
||||
T_QUOTED_STRING = 283,
|
||||
T_INTEGER = 284,
|
||||
T_DOUBLE = 285,
|
||||
T_PARAMETER = 286,
|
||||
T_DATA_SOURCE_PARAMETER = 287,
|
||||
T_ASSIGN = 288,
|
||||
T_NOT = 289,
|
||||
T_AND = 290,
|
||||
T_OR = 291,
|
||||
T_NIN = 292,
|
||||
T_REGEX_MATCH = 293,
|
||||
T_REGEX_NON_MATCH = 294,
|
||||
T_EQ = 295,
|
||||
T_NE = 296,
|
||||
T_LT = 297,
|
||||
T_GT = 298,
|
||||
T_LE = 299,
|
||||
T_GE = 300,
|
||||
T_LIKE = 301,
|
||||
T_PLUS = 302,
|
||||
T_MINUS = 303,
|
||||
T_TIMES = 304,
|
||||
T_DIV = 305,
|
||||
T_MOD = 306,
|
||||
T_QUESTION = 307,
|
||||
T_COLON = 308,
|
||||
T_SCOPE = 309,
|
||||
T_RANGE = 310,
|
||||
T_COMMA = 311,
|
||||
T_OPEN = 312,
|
||||
T_CLOSE = 313,
|
||||
T_OBJECT_OPEN = 314,
|
||||
T_OBJECT_CLOSE = 315,
|
||||
T_ARRAY_OPEN = 316,
|
||||
T_ARRAY_CLOSE = 317,
|
||||
T_OUTBOUND = 318,
|
||||
T_INBOUND = 319,
|
||||
T_ANY = 320,
|
||||
T_ALL = 321,
|
||||
T_NONE = 322,
|
||||
UMINUS = 323,
|
||||
UPLUS = 324,
|
||||
FUNCCALL = 325,
|
||||
REFERENCE = 326,
|
||||
INDEXED = 327,
|
||||
EXPANSION = 328
|
||||
T_K_SHORTEST_PATHS = 273,
|
||||
T_DISTINCT = 274,
|
||||
T_REMOVE = 275,
|
||||
T_INSERT = 276,
|
||||
T_UPDATE = 277,
|
||||
T_REPLACE = 278,
|
||||
T_UPSERT = 279,
|
||||
T_NULL = 280,
|
||||
T_TRUE = 281,
|
||||
T_FALSE = 282,
|
||||
T_STRING = 283,
|
||||
T_QUOTED_STRING = 284,
|
||||
T_INTEGER = 285,
|
||||
T_DOUBLE = 286,
|
||||
T_PARAMETER = 287,
|
||||
T_DATA_SOURCE_PARAMETER = 288,
|
||||
T_ASSIGN = 289,
|
||||
T_NOT = 290,
|
||||
T_AND = 291,
|
||||
T_OR = 292,
|
||||
T_NIN = 293,
|
||||
T_REGEX_MATCH = 294,
|
||||
T_REGEX_NON_MATCH = 295,
|
||||
T_EQ = 296,
|
||||
T_NE = 297,
|
||||
T_LT = 298,
|
||||
T_GT = 299,
|
||||
T_LE = 300,
|
||||
T_GE = 301,
|
||||
T_LIKE = 302,
|
||||
T_PLUS = 303,
|
||||
T_MINUS = 304,
|
||||
T_TIMES = 305,
|
||||
T_DIV = 306,
|
||||
T_MOD = 307,
|
||||
T_QUESTION = 308,
|
||||
T_COLON = 309,
|
||||
T_SCOPE = 310,
|
||||
T_RANGE = 311,
|
||||
T_COMMA = 312,
|
||||
T_OPEN = 313,
|
||||
T_CLOSE = 314,
|
||||
T_OBJECT_OPEN = 315,
|
||||
T_OBJECT_CLOSE = 316,
|
||||
T_ARRAY_OPEN = 317,
|
||||
T_ARRAY_CLOSE = 318,
|
||||
T_OUTBOUND = 319,
|
||||
T_INBOUND = 320,
|
||||
T_ANY = 321,
|
||||
T_ALL = 322,
|
||||
T_NONE = 323,
|
||||
UMINUS = 324,
|
||||
UPLUS = 325,
|
||||
FUNCCALL = 326,
|
||||
REFERENCE = 327,
|
||||
INDEXED = 328,
|
||||
EXPANSION = 329
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@ -125,7 +130,7 @@ extern int Aqldebug;
|
|||
|
||||
union YYSTYPE
|
||||
{
|
||||
#line 35 "Aql/grammar.y" /* yacc.c:1915 */
|
||||
#line 35 "Aql/grammar.y" /* yacc.c:1921 */
|
||||
|
||||
arangodb::aql::AstNode* node;
|
||||
struct {
|
||||
|
@ -135,7 +140,7 @@ union YYSTYPE
|
|||
bool boolval;
|
||||
int64_t intval;
|
||||
|
||||
#line 139 "Aql/grammar.hpp" /* yacc.c:1915 */
|
||||
#line 144 "Aql/grammar.hpp" /* yacc.c:1921 */
|
||||
};
|
||||
|
||||
typedef union YYSTYPE YYSTYPE;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
/* A Bison parser, made by GNU Bison 3.0.4. */
|
||||
/* A Bison parser, made by GNU Bison 3.3.2. */
|
||||
|
||||
/* Bison interface for Yacc-like parsers in C
|
||||
|
||||
Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
|
||||
Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2019 Free Software Foundation,
|
||||
Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -30,6 +31,9 @@
|
|||
This special exception was added by the Free Software Foundation in
|
||||
version 2.2 of Bison. */
|
||||
|
||||
/* Undocumented macros, especially those whose name start with YY_,
|
||||
are private implementation details. Do not rely on them. */
|
||||
|
||||
#ifndef YY_AQL_AQL_GRAMMAR_HPP_INCLUDED
|
||||
# define YY_AQL_AQL_GRAMMAR_HPP_INCLUDED
|
||||
/* Debug traces. */
|
||||
|
@ -61,62 +65,63 @@ extern int Aqldebug;
|
|||
T_AGGREGATE = 270,
|
||||
T_GRAPH = 271,
|
||||
T_SHORTEST_PATH = 272,
|
||||
T_DISTINCT = 273,
|
||||
T_REMOVE = 274,
|
||||
T_INSERT = 275,
|
||||
T_UPDATE = 276,
|
||||
T_REPLACE = 277,
|
||||
T_UPSERT = 278,
|
||||
T_NULL = 279,
|
||||
T_TRUE = 280,
|
||||
T_FALSE = 281,
|
||||
T_STRING = 282,
|
||||
T_QUOTED_STRING = 283,
|
||||
T_INTEGER = 284,
|
||||
T_DOUBLE = 285,
|
||||
T_PARAMETER = 286,
|
||||
T_DATA_SOURCE_PARAMETER = 287,
|
||||
T_ASSIGN = 288,
|
||||
T_NOT = 289,
|
||||
T_AND = 290,
|
||||
T_OR = 291,
|
||||
T_NIN = 292,
|
||||
T_REGEX_MATCH = 293,
|
||||
T_REGEX_NON_MATCH = 294,
|
||||
T_EQ = 295,
|
||||
T_NE = 296,
|
||||
T_LT = 297,
|
||||
T_GT = 298,
|
||||
T_LE = 299,
|
||||
T_GE = 300,
|
||||
T_LIKE = 301,
|
||||
T_PLUS = 302,
|
||||
T_MINUS = 303,
|
||||
T_TIMES = 304,
|
||||
T_DIV = 305,
|
||||
T_MOD = 306,
|
||||
T_QUESTION = 307,
|
||||
T_COLON = 308,
|
||||
T_SCOPE = 309,
|
||||
T_RANGE = 310,
|
||||
T_COMMA = 311,
|
||||
T_OPEN = 312,
|
||||
T_CLOSE = 313,
|
||||
T_OBJECT_OPEN = 314,
|
||||
T_OBJECT_CLOSE = 315,
|
||||
T_ARRAY_OPEN = 316,
|
||||
T_ARRAY_CLOSE = 317,
|
||||
T_OUTBOUND = 318,
|
||||
T_INBOUND = 319,
|
||||
T_ANY = 320,
|
||||
T_ALL = 321,
|
||||
T_NONE = 322,
|
||||
UMINUS = 323,
|
||||
UPLUS = 324,
|
||||
FUNCCALL = 325,
|
||||
REFERENCE = 326,
|
||||
INDEXED = 327,
|
||||
EXPANSION = 328
|
||||
T_K_SHORTEST_PATHS = 273,
|
||||
T_DISTINCT = 274,
|
||||
T_REMOVE = 275,
|
||||
T_INSERT = 276,
|
||||
T_UPDATE = 277,
|
||||
T_REPLACE = 278,
|
||||
T_UPSERT = 279,
|
||||
T_NULL = 280,
|
||||
T_TRUE = 281,
|
||||
T_FALSE = 282,
|
||||
T_STRING = 283,
|
||||
T_QUOTED_STRING = 284,
|
||||
T_INTEGER = 285,
|
||||
T_DOUBLE = 286,
|
||||
T_PARAMETER = 287,
|
||||
T_DATA_SOURCE_PARAMETER = 288,
|
||||
T_ASSIGN = 289,
|
||||
T_NOT = 290,
|
||||
T_AND = 291,
|
||||
T_OR = 292,
|
||||
T_NIN = 293,
|
||||
T_REGEX_MATCH = 294,
|
||||
T_REGEX_NON_MATCH = 295,
|
||||
T_EQ = 296,
|
||||
T_NE = 297,
|
||||
T_LT = 298,
|
||||
T_GT = 299,
|
||||
T_LE = 300,
|
||||
T_GE = 301,
|
||||
T_LIKE = 302,
|
||||
T_PLUS = 303,
|
||||
T_MINUS = 304,
|
||||
T_TIMES = 305,
|
||||
T_DIV = 306,
|
||||
T_MOD = 307,
|
||||
T_QUESTION = 308,
|
||||
T_COLON = 309,
|
||||
T_SCOPE = 310,
|
||||
T_RANGE = 311,
|
||||
T_COMMA = 312,
|
||||
T_OPEN = 313,
|
||||
T_CLOSE = 314,
|
||||
T_OBJECT_OPEN = 315,
|
||||
T_OBJECT_CLOSE = 316,
|
||||
T_ARRAY_OPEN = 317,
|
||||
T_ARRAY_CLOSE = 318,
|
||||
T_OUTBOUND = 319,
|
||||
T_INBOUND = 320,
|
||||
T_ANY = 321,
|
||||
T_ALL = 322,
|
||||
T_NONE = 323,
|
||||
UMINUS = 324,
|
||||
UPLUS = 325,
|
||||
FUNCCALL = 326,
|
||||
REFERENCE = 327,
|
||||
INDEXED = 328,
|
||||
EXPANSION = 329
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@ -125,7 +130,7 @@ extern int Aqldebug;
|
|||
|
||||
union YYSTYPE
|
||||
{
|
||||
#line 35 "Aql/grammar.y" /* yacc.c:1915 */
|
||||
#line 35 "Aql/grammar.y" /* yacc.c:1921 */
|
||||
|
||||
arangodb::aql::AstNode* node;
|
||||
struct {
|
||||
|
@ -135,7 +140,7 @@ union YYSTYPE
|
|||
bool boolval;
|
||||
int64_t intval;
|
||||
|
||||
#line 139 "Aql/grammar.hpp" /* yacc.c:1915 */
|
||||
#line 144 "Aql/grammar.hpp" /* yacc.c:1921 */
|
||||
};
|
||||
|
||||
typedef union YYSTYPE YYSTYPE;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// Bison reports all its errors via the function `Aqlerror`, which
|
||||
// will receive the error message as a constant string. So we
|
||||
// must not free the string inside `Aqlerror`, and we cannot even
|
||||
// tell if the error message is a dynamically allocated error
|
||||
// tell if the error message is a dynamically allocated error
|
||||
// message or a hard-coded error message that resides in some
|
||||
// static part of the program.
|
||||
// Even worse, `Aqlerror` does not return control to Bison but throws
|
||||
|
@ -55,9 +55,9 @@ using namespace arangodb::aql;
|
|||
|
||||
/// @brief forward for lexer function defined in Aql/tokens.ll
|
||||
int Aqllex(YYSTYPE*, YYLTYPE*, void*);
|
||||
|
||||
|
||||
/// @brief register parse error (this will also abort the currently running query)
|
||||
void Aqlerror(YYLTYPE* locp,
|
||||
void Aqlerror(YYLTYPE* locp,
|
||||
arangodb::aql::Parser* parser,
|
||||
char const* message) {
|
||||
parser->registerParseError(TRI_ERROR_QUERY_PARSE, message, locp->first_line, locp->first_column);
|
||||
|
@ -71,7 +71,7 @@ static void CheckIntoVariables(Parser* parser, AstNode const* expression,
|
|||
if (expression == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
arangodb::HashSet<Variable const*> varsInAssignment;
|
||||
Ast::getReferencedVariables(expression, varsInAssignment);
|
||||
|
||||
|
@ -85,12 +85,12 @@ static void CheckIntoVariables(Parser* parser, AstNode const* expression,
|
|||
}
|
||||
|
||||
/// @brief register variables in the scope
|
||||
static void RegisterAssignVariables(Parser* parser, arangodb::aql::Scopes* scopes,
|
||||
static void RegisterAssignVariables(Parser* parser, arangodb::aql::Scopes* scopes,
|
||||
int line, int column,
|
||||
arangodb::HashSet<Variable const*>& variablesIntroduced,
|
||||
arangodb::HashSet<Variable const*>& variablesIntroduced,
|
||||
AstNode const* vars) {
|
||||
arangodb::HashSet<Variable const*> varsInAssignment;
|
||||
|
||||
|
||||
size_t const n = vars->numMembers();
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
|
@ -99,7 +99,7 @@ static void RegisterAssignVariables(Parser* parser, arangodb::aql::Scopes* scope
|
|||
if (member != nullptr) {
|
||||
TRI_ASSERT(member->type == NODE_TYPE_ASSIGN);
|
||||
// check if any of the assignment refers to a variable introduced by this very
|
||||
// same COLLECT, e.g. COLLECT aggregate x = .., y = x
|
||||
// same COLLECT, e.g. COLLECT aggregate x = .., y = x
|
||||
varsInAssignment.clear();
|
||||
Ast::getReferencedVariables(member->getMember(1), varsInAssignment);
|
||||
for (auto const& it : varsInAssignment) {
|
||||
|
@ -127,7 +127,7 @@ static bool ValidateAggregates(Parser* parser, AstNode const* aggregates) {
|
|||
|
||||
if (member != nullptr) {
|
||||
TRI_ASSERT(member->type == NODE_TYPE_ASSIGN);
|
||||
|
||||
|
||||
auto func = member->getMember(1);
|
||||
|
||||
bool isValid = true;
|
||||
|
@ -154,7 +154,7 @@ static bool ValidateAggregates(Parser* parser, AstNode const* aggregates) {
|
|||
}
|
||||
|
||||
/// @brief start a new scope for the collect
|
||||
static bool StartCollectScope(arangodb::aql::Scopes* scopes) {
|
||||
static bool StartCollectScope(arangodb::aql::Scopes* scopes) {
|
||||
// check if we are in the main scope
|
||||
if (scopes->type() == arangodb::aql::AQL_SCOPE_MAIN) {
|
||||
return false;
|
||||
|
@ -231,6 +231,7 @@ static AstNode* TransformOutputVariables(Parser* parser, AstNode const* names) {
|
|||
|
||||
%token T_GRAPH "GRAPH keyword"
|
||||
%token T_SHORTEST_PATH "SHORTEST_PATH keyword"
|
||||
%token T_K_SHORTEST_PATHS "K_SHORTEST_PATHS keyword"
|
||||
%token T_DISTINCT "DISTINCT modifier"
|
||||
|
||||
%token T_REMOVE "REMOVE command"
|
||||
|
@ -352,6 +353,7 @@ static AstNode* TransformOutputVariables(Parser* parser, AstNode const* names) {
|
|||
%type <node> for_output_variables;
|
||||
%type <node> traversal_graph_info;
|
||||
%type <node> shortest_path_graph_info;
|
||||
%type <node> k_shortest_paths_graph_info;
|
||||
%type <node> optional_array_elements;
|
||||
%type <node> array_elements_list;
|
||||
%type <node> for_options;
|
||||
|
@ -607,7 +609,28 @@ shortest_path_graph_info:
|
|||
}
|
||||
;
|
||||
|
||||
|
||||
k_shortest_paths_graph_info:
|
||||
graph_direction T_K_SHORTEST_PATHS expression T_STRING expression graph_subject options {
|
||||
if (!TRI_CaseEqualString($4.value, "TO")) {
|
||||
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'TO'", $4.value, yylloc.first_line, yylloc.first_column);
|
||||
}
|
||||
auto infoNode = parser->ast()->createNodeArray();
|
||||
auto dirNode = parser->ast()->createNodeValueInt($1);
|
||||
// Direction
|
||||
infoNode->addMember(dirNode);
|
||||
// Source
|
||||
infoNode->addMember($3);
|
||||
// Target
|
||||
infoNode->addMember($5);
|
||||
// Graph
|
||||
infoNode->addMember($6);
|
||||
// Opts
|
||||
auto opts = parser->ast()->createNodeOptions($7);
|
||||
TRI_ASSERT(opts != nullptr);
|
||||
infoNode->addMember(opts);
|
||||
$$ = infoNode;
|
||||
}
|
||||
;
|
||||
|
||||
for_statement:
|
||||
T_FOR for_output_variables T_IN expression {
|
||||
|
@ -633,10 +656,10 @@ for_statement:
|
|||
AstNode* variableNode = static_cast<AstNode*>(parser->popStack());
|
||||
|
||||
Variable* variable = static_cast<Variable*>(variableNode->getData());
|
||||
|
||||
AstNode* node = nullptr;
|
||||
AstNode* search = nullptr;
|
||||
AstNode* options = nullptr;
|
||||
|
||||
AstNode* node = nullptr;
|
||||
AstNode* search = nullptr;
|
||||
AstNode* options = nullptr;
|
||||
|
||||
if ($6 != nullptr) {
|
||||
// we got a SEARCH and/or OPTIONS clause
|
||||
|
@ -656,16 +679,16 @@ for_statement:
|
|||
if (search != nullptr) {
|
||||
// we got a SEARCH clause. this is always a view.
|
||||
node = parser->ast()->createNodeForView(variable, $4, search, options);
|
||||
|
||||
|
||||
if ($4->type != NODE_TYPE_PARAMETER_DATASOURCE &&
|
||||
$4->type != NODE_TYPE_VIEW &&
|
||||
$4->type != NODE_TYPE_VIEW &&
|
||||
$4->type != NODE_TYPE_COLLECTION) {
|
||||
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "SEARCH condition used on non-view", yylloc.first_line, yylloc.first_column);
|
||||
}
|
||||
} else {
|
||||
node = parser->ast()->createNodeFor(variable, $4, options);
|
||||
}
|
||||
|
||||
|
||||
parser->ast()->addOperation(node);
|
||||
}
|
||||
| T_FOR for_output_variables T_IN traversal_graph_info {
|
||||
|
@ -707,8 +730,27 @@ for_statement:
|
|||
TRI_ASSERT(graphInfoNode->type == NODE_TYPE_ARRAY);
|
||||
auto node = parser->ast()->createNodeShortestPath(variablesNode, graphInfoNode);
|
||||
parser->ast()->addOperation(node);
|
||||
|
||||
|
||||
}
|
||||
| T_FOR for_output_variables T_IN k_shortest_paths_graph_info {
|
||||
// first open a new scope (after expression is evaluated)
|
||||
parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR);
|
||||
// K Shortest Paths
|
||||
auto variableNamesNode = static_cast<AstNode*>($2);
|
||||
TRI_ASSERT(variableNamesNode != nullptr);
|
||||
TRI_ASSERT(variableNamesNode->type == NODE_TYPE_ARRAY);
|
||||
if (variableNamesNode->numMembers() > 2) {
|
||||
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "ShortestPath only has one or two return variables", yylloc.first_line, yylloc.first_column);
|
||||
}
|
||||
auto variablesNode = TransformOutputVariables(parser, variableNamesNode);
|
||||
auto graphInfoNode = static_cast<AstNode*>($4);
|
||||
TRI_ASSERT(graphInfoNode != nullptr);
|
||||
TRI_ASSERT(graphInfoNode->type == NODE_TYPE_ARRAY);
|
||||
auto node = parser->ast()->createNodeKShortestPaths(variablesNode, graphInfoNode);
|
||||
parser->ast()->addOperation(node);
|
||||
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
filter_statement:
|
||||
|
@ -1181,11 +1223,11 @@ upsert_statement:
|
|||
auto index = parser->ast()->createNodeValueInt(0);
|
||||
auto firstDoc = parser->ast()->createNodeLet(variableNode, parser->ast()->createNodeIndexedAccess(parser->ast()->createNodeReference(subqueryName), index));
|
||||
parser->ast()->addOperation(firstDoc);
|
||||
|
||||
|
||||
parser->pushStack(forNode);
|
||||
} T_INSERT expression update_or_replace expression in_or_into_collection options {
|
||||
AstNode* forNode = static_cast<AstNode*>(parser->popStack());
|
||||
forNode->changeMember(1, $9);
|
||||
forNode->changeMember(1, $9);
|
||||
|
||||
if (!parser->configureWriteQuery($9, $10)) {
|
||||
YYABORT;
|
||||
|
@ -1482,7 +1524,7 @@ for_options:
|
|||
// found SEARCH
|
||||
node->addMember($2);
|
||||
node->addMember(parser->ast()->createNodeNop());
|
||||
} else {
|
||||
} else {
|
||||
// everything else must be OPTIONS
|
||||
if (!TRI_CaseEqualString($1.value, "OPTIONS")) {
|
||||
parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'SEARCH' or 'OPTIONS'", $1.value, yylloc.first_line, yylloc.first_column);
|
||||
|
@ -1498,7 +1540,7 @@ for_options:
|
|||
if ($2 == nullptr) {
|
||||
ABORT_OOM
|
||||
}
|
||||
|
||||
|
||||
// two extra qualifiers. we expect them in the order: SEARCH, then OPTIONS
|
||||
|
||||
if (!TRI_CaseEqualString($1.value, "SEARCH") ||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -162,6 +162,10 @@ class Parser;
|
|||
return T_SHORTEST_PATH;
|
||||
}
|
||||
|
||||
(?i:K_SHORTEST_PATHS) {
|
||||
return T_K_SHORTEST_PATHS;
|
||||
}
|
||||
|
||||
(?i:OUTBOUND) {
|
||||
return T_OUTBOUND;
|
||||
}
|
||||
|
|
|
@ -229,6 +229,8 @@ SET(ARANGOD_SOURCES
|
|||
Aql/IndexHint.cpp
|
||||
Aql/IndexNode.cpp
|
||||
Aql/InputAqlItemRow.cpp
|
||||
Aql/KShortestPathsExecutor.cpp
|
||||
Aql/KShortestPathsNode.cpp
|
||||
Aql/LimitExecutor.cpp
|
||||
Aql/ModificationExecutor.cpp
|
||||
Aql/ModificationExecutorTraits.cpp
|
||||
|
@ -378,6 +380,7 @@ SET(ARANGOD_SOURCES
|
|||
Graph/Graph.cpp
|
||||
Graph/GraphManager.cpp
|
||||
Graph/GraphOperations.cpp
|
||||
Graph/KShortestPathsFinder.cpp
|
||||
Graph/NeighborsEnumerator.cpp
|
||||
Graph/PathEnumerator.cpp
|
||||
Graph/ShortestPathFinder.cpp
|
||||
|
|
|
@ -74,6 +74,14 @@ struct EdgeDocumentToken {
|
|||
#endif
|
||||
}
|
||||
|
||||
EdgeDocumentToken& operator=(EdgeDocumentToken const& edtkn) {
|
||||
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
|
||||
_type = edtkn._type;
|
||||
#endif
|
||||
_data = edtkn._data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
EdgeDocumentToken& operator=(EdgeDocumentToken&& edtkn) {
|
||||
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
|
||||
_type = edtkn._type;
|
||||
|
|
|
@ -0,0 +1,381 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// 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 Markus Pfeiffer
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "KShortestPathsFinder.h"
|
||||
|
||||
#include "Aql/AqlValue.h"
|
||||
#include "Cluster/ServerState.h"
|
||||
#include "Graph/EdgeCursor.h"
|
||||
#include "Graph/EdgeDocumentToken.h"
|
||||
#include "Graph/ShortestPathOptions.h"
|
||||
#include "Graph/ShortestPathResult.h"
|
||||
#include "Graph/TraverserCache.h"
|
||||
#include "Transaction/Helpers.h"
|
||||
#include "Utils/OperationCursor.h"
|
||||
#include "VocBase/LogicalCollection.h"
|
||||
|
||||
#include <velocypack/Iterator.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/StringRef.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::graph;
|
||||
|
||||
//
|
||||
KShortestPathsFinder::KShortestPathsFinder(ShortestPathOptions& options)
|
||||
: ShortestPathFinder(options), _pathAvailable(false) {}
|
||||
KShortestPathsFinder::~KShortestPathsFinder() {}
|
||||
|
||||
// Sets up k-shortest-paths traversal from start to end
|
||||
// Returns number of currently known paths
|
||||
bool KShortestPathsFinder::startKShortestPathsTraversal(
|
||||
arangodb::velocypack::Slice const& start, arangodb::velocypack::Slice const& end) {
|
||||
_start = arangodb::velocypack::StringRef(start);
|
||||
_end = arangodb::velocypack::StringRef(end);
|
||||
_pathAvailable = true;
|
||||
_shortestPaths.clear();
|
||||
_candidatePaths.clear();
|
||||
|
||||
TRI_IF_FAILURE("TraversalOOMInitialize") {
|
||||
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KShortestPathsFinder::computeShortestPath(
|
||||
VertexRef const& start, VertexRef const& end,
|
||||
std::unordered_set<VertexRef> const& forbiddenVertices,
|
||||
std::unordered_set<Edge> const& forbiddenEdges, Path& result) {
|
||||
bool found = false;
|
||||
Ball left(start, FORWARD);
|
||||
Ball right(end, BACKWARD);
|
||||
VertexRef join;
|
||||
|
||||
result.clear();
|
||||
|
||||
while (!left._frontier.empty() && !right._frontier.empty() && !found) {
|
||||
_options.isQueryKilledCallback();
|
||||
|
||||
// Choose the smaller frontier to expand.
|
||||
if (left._frontier.size() < right._frontier.size()) {
|
||||
found = advanceFrontier(left, right, forbiddenVertices, forbiddenEdges, join);
|
||||
} else {
|
||||
found = advanceFrontier(right, left, forbiddenVertices, forbiddenEdges, join);
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
reconstructPath(left, right, join, result);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void KShortestPathsFinder::computeNeighbourhoodOfVertex(
|
||||
VertexRef vertex, Direction direction, std::vector<Step>& steps) {
|
||||
std::unique_ptr<EdgeCursor> edgeCursor;
|
||||
|
||||
switch (direction) {
|
||||
case BACKWARD:
|
||||
edgeCursor.reset(_options.nextReverseCursor(vertex));
|
||||
break;
|
||||
case FORWARD:
|
||||
edgeCursor.reset(_options.nextCursor(vertex));
|
||||
break;
|
||||
default:
|
||||
TRI_ASSERT(false);
|
||||
}
|
||||
|
||||
// TODO: This is a bit of a hack
|
||||
if (_options.useWeight()) {
|
||||
auto callback = [&](EdgeDocumentToken&& eid, VPackSlice edge, size_t cursorIdx) -> void {
|
||||
if (edge.isString()) {
|
||||
VPackSlice doc = _options.cache()->lookupToken(eid);
|
||||
double weight = _options.weightEdge(doc);
|
||||
if (edge.compareString(vertex.data(), vertex.length()) != 0) {
|
||||
VertexRef id = _options.cache()->persistString(VertexRef(edge));
|
||||
steps.emplace_back(std::move(eid), id, weight);
|
||||
}
|
||||
} else {
|
||||
VertexRef other(transaction::helpers::extractFromFromDocument(edge));
|
||||
if (other == vertex) {
|
||||
other = VertexRef(transaction::helpers::extractToFromDocument(edge));
|
||||
}
|
||||
if (other != vertex) {
|
||||
VertexRef id = _options.cache()->persistString(other);
|
||||
steps.emplace_back(std::move(eid), id, _options.weightEdge(edge));
|
||||
}
|
||||
}
|
||||
};
|
||||
edgeCursor->readAll(callback);
|
||||
} else {
|
||||
auto callback = [&](EdgeDocumentToken&& eid, VPackSlice edge, size_t cursorIdx) -> void {
|
||||
if (edge.isString()) {
|
||||
if (edge.compareString(vertex.data(), vertex.length()) != 0) {
|
||||
VertexRef id = _options.cache()->persistString(VertexRef(edge));
|
||||
steps.emplace_back(std::move(eid), id, 1);
|
||||
}
|
||||
} else {
|
||||
VertexRef other(transaction::helpers::extractFromFromDocument(edge));
|
||||
if (other == vertex) {
|
||||
other = VertexRef(transaction::helpers::extractToFromDocument(edge));
|
||||
}
|
||||
if (other != vertex) {
|
||||
VertexRef id = _options.cache()->persistString(other);
|
||||
steps.emplace_back(std::move(eid), id, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
edgeCursor->readAll(callback);
|
||||
}
|
||||
}
|
||||
|
||||
bool KShortestPathsFinder::advanceFrontier(
|
||||
Ball& source, Ball const& target, std::unordered_set<VertexRef> const& forbiddenVertices,
|
||||
std::unordered_set<Edge> const& forbiddenEdges, VertexRef& join) {
|
||||
std::vector<Step> neighbours;
|
||||
VertexRef vr;
|
||||
FoundVertex* v;
|
||||
|
||||
bool success = source._frontier.popMinimal(vr, v, true);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
neighbours.clear();
|
||||
computeNeighbourhoodOfVertex(vr, source._direction, neighbours);
|
||||
|
||||
for (auto& s : neighbours) {
|
||||
if (forbiddenEdges.find(s._edge) == forbiddenEdges.end() &&
|
||||
forbiddenVertices.find(s._vertex) == forbiddenVertices.end()) {
|
||||
double weight = v->weight() + s._weight;
|
||||
|
||||
auto lookup = source._frontier.find(s._vertex);
|
||||
if (lookup != nullptr) {
|
||||
if (lookup->weight() > weight) {
|
||||
source._frontier.lowerWeight(s._vertex, weight);
|
||||
lookup->_pred = vr;
|
||||
lookup->_edge = s._edge;
|
||||
}
|
||||
} else {
|
||||
source._frontier.insert(s._vertex,
|
||||
new FoundVertex(s._vertex, vr, std::move(s._edge), weight));
|
||||
|
||||
auto found = target._frontier.find(s._vertex);
|
||||
if (found != nullptr) {
|
||||
join = s._vertex;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void KShortestPathsFinder::reconstructPath(Ball const& left, Ball const& right,
|
||||
VertexRef const& join,
|
||||
Path& result) {
|
||||
result.clear();
|
||||
TRI_ASSERT(!join.empty());
|
||||
result._vertices.emplace_back(join);
|
||||
|
||||
FoundVertex* it;
|
||||
it = left._frontier.find(join);
|
||||
TRI_ASSERT(it != nullptr);
|
||||
double startToJoin = it->weight();
|
||||
result._weight = startToJoin;
|
||||
while (it != nullptr && it->_weight > 0) {
|
||||
result._vertices.push_front(it->_pred);
|
||||
result._edges.push_front(it->_edge);
|
||||
result._weights.push_front(it->_weight);
|
||||
it = left._frontier.find(it->_pred);
|
||||
}
|
||||
// Initial vertex has weight 0
|
||||
result._weights.push_front(0);
|
||||
|
||||
it = right._frontier.find(join);
|
||||
TRI_ASSERT(it != nullptr);
|
||||
double joinToEnd = it->_weight;
|
||||
result._weight += joinToEnd;
|
||||
while (it != nullptr && it->_weight > 0) {
|
||||
result._vertices.emplace_back(it->_pred);
|
||||
result._edges.emplace_back(it->_edge);
|
||||
it = right._frontier.find(it->_pred);
|
||||
TRI_ASSERT(it != nullptr); // should run into 0 weight before
|
||||
result._weights.emplace_back(startToJoin + (joinToEnd - it->_weight));
|
||||
}
|
||||
|
||||
TRI_IF_FAILURE("TraversalOOMPath") {
|
||||
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
bool KShortestPathsFinder::computeNextShortestPath(Path& result) {
|
||||
std::unordered_set<VertexRef> forbiddenVertices;
|
||||
std::unordered_set<Edge> forbiddenEdges;
|
||||
Path tmpPath, candidate;
|
||||
TRI_ASSERT(!_shortestPaths.empty());
|
||||
auto& lastShortestPath = _shortestPaths.back();
|
||||
bool available = false;
|
||||
|
||||
for (size_t i = 0; i < lastShortestPath.length() - 1; ++i) {
|
||||
auto& spur = lastShortestPath._vertices.at(i);
|
||||
|
||||
forbiddenVertices.clear();
|
||||
forbiddenEdges.clear();
|
||||
|
||||
// Must not use vertices on the prefix
|
||||
for (size_t j = 0; j < i; ++j) {
|
||||
forbiddenVertices.emplace(lastShortestPath._vertices[j]);
|
||||
}
|
||||
|
||||
// TODO: This can be done more efficiently by storing shortest
|
||||
// paths in a prefix/postfix tree
|
||||
for (auto const& p : _shortestPaths) {
|
||||
bool eq = true;
|
||||
for (size_t e = 0; e < i; ++e) {
|
||||
if (!p._edges.at(e).equals(lastShortestPath._edges.at(e))) {
|
||||
eq = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (eq && (i < p._edges.size())) {
|
||||
forbiddenEdges.emplace(p._edges.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (computeShortestPath(spur, _end, forbiddenVertices, forbiddenEdges, tmpPath)) {
|
||||
candidate.clear();
|
||||
candidate.append(lastShortestPath, 0, i);
|
||||
candidate.append(tmpPath, 0, tmpPath.length() - 1);
|
||||
_candidatePaths.emplace_back(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_candidatePaths.empty()) {
|
||||
// TODO: hack, _candidatePaths should be a priority queue
|
||||
// indeed one that removes duplicates automatically
|
||||
// Sorted in reverse to have pop_back
|
||||
if (_options.useWeight()) {
|
||||
std::sort(_candidatePaths.begin(), _candidatePaths.end(), [](Path const& p1, Path const& p2) {
|
||||
return p1._weight > p2._weight;
|
||||
});
|
||||
} else {
|
||||
std::sort(_candidatePaths.begin(), _candidatePaths.end(), [](Path const& p1, Path const& p2) {
|
||||
return p1._vertices.size() > p2._vertices.size();
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: this is of course bad.
|
||||
_candidatePaths.erase(std::unique(_candidatePaths.begin(), _candidatePaths.end()),
|
||||
_candidatePaths.end());
|
||||
|
||||
auto const& p = _candidatePaths.back();
|
||||
result.clear();
|
||||
result.append(p, 0, p.length() - 1);
|
||||
_candidatePaths.pop_back();
|
||||
available = true;
|
||||
}
|
||||
return available;
|
||||
}
|
||||
|
||||
bool KShortestPathsFinder::getNextPath(Path& result) {
|
||||
bool available = false;
|
||||
result.clear();
|
||||
|
||||
// TODO: this looks a bit ugly
|
||||
if (_shortestPaths.empty()) {
|
||||
if (_start == _end) {
|
||||
TRI_ASSERT(!_start.empty());
|
||||
result._vertices.emplace_back(_start);
|
||||
result._weight = 0;
|
||||
available = true;
|
||||
} else {
|
||||
available = computeShortestPath(_start, _end, {}, {}, result);
|
||||
}
|
||||
} else {
|
||||
if (_start == _end) {
|
||||
available = false;
|
||||
} else {
|
||||
available = computeNextShortestPath(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (available) {
|
||||
_shortestPaths.emplace_back(result);
|
||||
_options.fetchVerticesCoordinator(result._vertices);
|
||||
|
||||
TRI_IF_FAILURE("TraversalOOMPath") {
|
||||
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
|
||||
}
|
||||
}
|
||||
_pathAvailable = available;
|
||||
return available;
|
||||
}
|
||||
|
||||
bool KShortestPathsFinder::getNextPathShortestPathResult(ShortestPathResult& result) {
|
||||
Path path;
|
||||
|
||||
result.clear();
|
||||
if (getNextPath(path)) {
|
||||
result._vertices = path._vertices;
|
||||
result._edges = path._edges;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool KShortestPathsFinder::getNextPathAql(arangodb::velocypack::Builder& result) {
|
||||
Path path;
|
||||
|
||||
if (getNextPath(path)) {
|
||||
result.clear();
|
||||
result.openObject();
|
||||
|
||||
result.add(VPackValue("edges"));
|
||||
result.openArray();
|
||||
for (auto const& it : path._edges) {
|
||||
_options.cache()->insertEdgeIntoResult(it, result);
|
||||
}
|
||||
result.close(); // Array
|
||||
|
||||
result.add(VPackValue("vertices"));
|
||||
result.openArray();
|
||||
for (auto const& it : path._vertices) {
|
||||
_options.cache()->insertVertexIntoResult(it, result);
|
||||
}
|
||||
result.close(); // Array
|
||||
if (_options.useWeight()) {
|
||||
result.add("weight", VPackValue(path._weight));
|
||||
} else {
|
||||
// If not using weight, weight is defined as 1 per edge
|
||||
result.add("weight", VPackValue(path._edges.size()));
|
||||
}
|
||||
result.close(); // Object
|
||||
TRI_ASSERT(result.isClosed());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2014-2019 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 Markus Pfeiffer
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef ARANGODB_GRAPH_CONSTANT_WEIGHT_K_SHORTEST_PATHS_FINDER_H
|
||||
#define ARANGODB_GRAPH_CONSTANT_WEIGHT_K_SHORTEST_PATHS_FINDER_H 1
|
||||
|
||||
#include "Aql/AqlValue.h"
|
||||
#include "Basics/VelocyPackHelper.h"
|
||||
#include "Graph/EdgeDocumentToken.h"
|
||||
#include "Graph/ShortestPathFinder.h"
|
||||
#include "Graph/ShortestPathPriorityQueue.h"
|
||||
|
||||
#include <velocypack/StringRef.h>
|
||||
|
||||
namespace arangodb {
|
||||
|
||||
namespace velocypack {
|
||||
class Slice;
|
||||
}
|
||||
|
||||
namespace graph {
|
||||
|
||||
struct ShortestPathOptions;
|
||||
|
||||
// Inherit from ShortestPathfinder to get destroyEngines and not copy it
|
||||
// again.
|
||||
// TODO: Traverser.h has destroyEngines as well (the code for the two functions
|
||||
// is identical), refactor?
|
||||
class KShortestPathsFinder : public ShortestPathFinder {
|
||||
private:
|
||||
// Mainly for readability
|
||||
typedef arangodb::velocypack::StringRef VertexRef;
|
||||
typedef arangodb::graph::EdgeDocumentToken Edge;
|
||||
enum Direction { FORWARD, BACKWARD };
|
||||
|
||||
// TODO: This could be merged with ShortestPathResult
|
||||
struct Path {
|
||||
std::deque<VertexRef> _vertices;
|
||||
std::deque<Edge> _edges;
|
||||
|
||||
// weight of path to vertex
|
||||
// where _weights.front() == 0 and
|
||||
// _weights.back() == _weight.
|
||||
std::deque<double> _weights;
|
||||
double _weight;
|
||||
|
||||
void clear() {
|
||||
_vertices.clear();
|
||||
_edges.clear();
|
||||
_weights.clear();
|
||||
_weight = 0;
|
||||
};
|
||||
size_t length() const { return _vertices.size(); };
|
||||
void append(Path const& p, size_t a, size_t b) {
|
||||
if (this->length() == 0) {
|
||||
_vertices.emplace_back(p._vertices.at(a));
|
||||
_weights.emplace_back(0);
|
||||
}
|
||||
// Only append paths where the first vertex of p
|
||||
// is the same as the last vertex of this.
|
||||
TRI_ASSERT((_vertices.back().equals(p._vertices.front())));
|
||||
|
||||
double ew = _weights.back();
|
||||
double pw = p._weights.at(a);
|
||||
while (a < b) {
|
||||
_edges.emplace_back(p._edges.at(a));
|
||||
a++;
|
||||
_vertices.emplace_back(p._vertices.at(a));
|
||||
_weights.emplace_back(ew + (p._weights.at(a) - pw));
|
||||
}
|
||||
_weight = _weights.back();
|
||||
};
|
||||
// TODO: implement == for EdgeDocumentToken and VertexRef
|
||||
// so these things become less cluttery
|
||||
bool operator==(Path const& rhs) const {
|
||||
if (_edges.size() != rhs._edges.size() ||
|
||||
_vertices.size() != rhs._vertices.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < _vertices.size(); ++i) {
|
||||
if (!_vertices.at(i).equals(rhs._vertices.at(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < _edges.size(); ++i) {
|
||||
if (!_edges.at(i).equals(rhs._edges.at(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
};
|
||||
struct Step {
|
||||
Edge _edge;
|
||||
VertexRef _vertex;
|
||||
double _weight;
|
||||
|
||||
Step(Edge const& edge, VertexRef const& vertex, double weight)
|
||||
: _edge(edge), _vertex(vertex), _weight(weight){};
|
||||
};
|
||||
|
||||
struct FoundVertex {
|
||||
VertexRef _vertex;
|
||||
VertexRef _pred;
|
||||
Edge _edge;
|
||||
double _weight;
|
||||
|
||||
// Using negative weight to signifiy start/end vertex
|
||||
FoundVertex(VertexRef const& vertex) : _vertex(vertex), _weight(0){};
|
||||
FoundVertex(VertexRef const& vertex, VertexRef const& pred, Edge&& edge, double weight)
|
||||
: _vertex(vertex), _pred(pred), _edge(std::move(edge)), _weight(weight){};
|
||||
double weight() { return _weight; };
|
||||
void setWeight(double weight) { _weight = weight; };
|
||||
VertexRef const& getKey() { return _vertex; };
|
||||
};
|
||||
// typedef std::deque<VertexRef> Frontier;
|
||||
typedef ShortestPathPriorityQueue<VertexRef, FoundVertex, double> Frontier;
|
||||
|
||||
// Contains the vertices that were found while searching
|
||||
// for a shortest path between start and end together with
|
||||
// the number of paths leading to that vertex and information
|
||||
// how to trace paths from the vertex from start/to end.
|
||||
typedef std::unordered_map<VertexRef, FoundVertex*> FoundVertices;
|
||||
|
||||
struct Ball {
|
||||
VertexRef _centre;
|
||||
Direction _direction;
|
||||
Frontier _frontier;
|
||||
|
||||
Ball(void){};
|
||||
Ball(VertexRef const& centre, Direction direction)
|
||||
: _centre(centre), _direction(direction) {
|
||||
auto v = new FoundVertex(centre);
|
||||
_frontier.insert(centre, v);
|
||||
};
|
||||
~Ball() {
|
||||
// TODO free all vertices
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
explicit KShortestPathsFinder(ShortestPathOptions& options);
|
||||
~KShortestPathsFinder();
|
||||
|
||||
// This is here because we inherit from ShortestPathFinder (to get the destroyEngines function)
|
||||
// TODO: Remove
|
||||
bool shortestPath(arangodb::velocypack::Slice const& start,
|
||||
arangodb::velocypack::Slice const& target,
|
||||
arangodb::graph::ShortestPathResult& result) {
|
||||
TRI_ASSERT(false);
|
||||
return false;
|
||||
};
|
||||
|
||||
//
|
||||
bool startKShortestPathsTraversal(arangodb::velocypack::Slice const& start,
|
||||
arangodb::velocypack::Slice const& end);
|
||||
|
||||
// get the next available path as AQL value.
|
||||
bool getNextPathAql(arangodb::velocypack::Builder& builder);
|
||||
// get the next available path as a ShortestPathResult
|
||||
// TODO: this is only here to not break catch-tests and needs a cleaner solution.
|
||||
// probably by making ShortestPathResult versatile enough and using that
|
||||
bool getNextPathShortestPathResult(ShortestPathResult& path);
|
||||
// get the next available path as a Path
|
||||
bool getNextPath(Path& path);
|
||||
bool isPathAvailable(void) { return _pathAvailable; };
|
||||
|
||||
private:
|
||||
bool computeShortestPath(VertexRef const& start, VertexRef const& end,
|
||||
std::unordered_set<VertexRef> const& forbiddenVertices,
|
||||
std::unordered_set<Edge> const& forbiddenEdges, Path& result);
|
||||
bool computeNextShortestPath(Path& result);
|
||||
|
||||
void reconstructPath(Ball const& left, Ball const& right,
|
||||
VertexRef const& join, Path& result);
|
||||
|
||||
void computeNeighbourhoodOfVertex(VertexRef vertex, Direction direction,
|
||||
std::vector<Step>& steps);
|
||||
|
||||
// returns the number of paths found
|
||||
// TODO: check why target can't be const
|
||||
bool advanceFrontier(Ball& source, Ball const& target,
|
||||
std::unordered_set<VertexRef> const& forbiddenVertices,
|
||||
std::unordered_set<Edge> const& forbiddenEdges, VertexRef& join);
|
||||
|
||||
private:
|
||||
bool _pathAvailable;
|
||||
|
||||
VertexRef _start;
|
||||
VertexRef _end;
|
||||
std::vector<Path> _shortestPaths;
|
||||
// TODO: This should be a priority queue
|
||||
std::vector<Path> _candidatePaths;
|
||||
};
|
||||
|
||||
} // namespace graph
|
||||
} // namespace arangodb
|
||||
#endif
|
|
@ -138,7 +138,7 @@ class ShortestPathPriorityQueue {
|
|||
/// than via lowerWeight, otherwise the queue order could be violated.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Value* find(Key const& k) {
|
||||
Value* find(Key const& k) const {
|
||||
auto it = _lookup.find(k);
|
||||
|
||||
if (it == _lookup.end()) {
|
||||
|
|
|
@ -46,12 +46,13 @@ namespace graph {
|
|||
|
||||
class AttributeWeightShortestPathFinder;
|
||||
class ConstantWeightShortestPathFinder;
|
||||
class KShortestPathsFinder;
|
||||
class TraverserCache;
|
||||
|
||||
class ShortestPathResult {
|
||||
friend class arangodb::graph::AttributeWeightShortestPathFinder;
|
||||
friend class arangodb::graph::ConstantWeightShortestPathFinder;
|
||||
|
||||
friend class arangodb::graph::KShortestPathsFinder;
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief Constructor. This is an abstract only class.
|
||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -3725,4 +3725,4 @@ var cutByResolution = function (str) {
|
|||
</div>
|
||||
</div></script><script id="warningList.ejs" type="text/template"> <% if (warnings.length > 0) { %> <div>
|
||||
<ul> <% console.log(warnings); _.each(warnings, function(w) { console.log(w);%> <li><b><%=w.code%></b>: <%=w.message%></li> <% }); %> </ul>
|
||||
</div> <% } %> </script></head><body><nav class="navbar" style="display: none"><div class="primary"><div class="navlogo"><a class="logo big" href="#"><img id="ArangoDBLogo" class="arangodbLogo" src="img/arangodb-edition-optimized.svg"></a><a class="logo small" href="#"><img class="arangodbLogo" src="img/arangodb_logo_small.png"></a><a class="version"><span id="currentVersion"></span></a></div><div class="statmenu" id="statisticBar"></div><div class="navmenu" id="navigationBar"></div></div></nav><div id="modalPlaceholder"></div><div class="bodyWrapper" style="display: none"><div class="centralRow"><div id="navbar2" class="navbarWrapper secondary"><div class="subnavmenu" id="subNavigationBar"></div></div><div class="resizecontainer contentWrapper"><div id="loadingScreen" class="loadingScreen" style="display: none"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw margin-bottom"></i> <span class="sr-only">Loading...</span></div><div id="content" class="centralContent"></div><footer class="footer"><div id="footerBar"></div></footer></div></div></div><div id="progressPlaceholder" style="display:none"></div><div id="spotlightPlaceholder" style="display:none"></div><div id="graphSettingsContent" style="display: none"></div><div id="filterSelectDiv" style="display:none"></div><div id="offlinePlaceholder" style="display:none"><div class="offline-div"><div class="pure-u"><div class="pure-u-1-4"></div><div class="pure-u-1-2 offline-window"><div class="offline-header"><h3>You have been disconnected from the server</h3></div><div class="offline-body"><p>The connection to the server has been lost. The server may be under heavy load.</p><p>Trying to reconnect in <span id="offlineSeconds">10</span> seconds.</p><p class="animation_state"><span><button class="button-success">Reconnect now</button></span></p></div></div><div class="pure-u-1-4"></div></div></div></div><div class="arangoFrame" style=""><div class="outerDiv"><div class="innerDiv"></div></div></div><script src="libs.js?version=1553626406176"></script><script src="app.js?version=1553626406176"></script></body></html>
|
||||
</div> <% } %> </script></head><body><nav class="navbar" style="display: none"><div class="primary"><div class="navlogo"><a class="logo big" href="#"><img id="ArangoDBLogo" class="arangodbLogo" src="img/arangodb-edition-optimized.svg"></a><a class="logo small" href="#"><img class="arangodbLogo" src="img/arangodb_logo_small.png"></a><a class="version"><span id="currentVersion"></span></a></div><div class="statmenu" id="statisticBar"></div><div class="navmenu" id="navigationBar"></div></div></nav><div id="modalPlaceholder"></div><div class="bodyWrapper" style="display: none"><div class="centralRow"><div id="navbar2" class="navbarWrapper secondary"><div class="subnavmenu" id="subNavigationBar"></div></div><div class="resizecontainer contentWrapper"><div id="loadingScreen" class="loadingScreen" style="display: none"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw margin-bottom"></i> <span class="sr-only">Loading...</span></div><div id="content" class="centralContent"></div><footer class="footer"><div id="footerBar"></div></footer></div></div></div><div id="progressPlaceholder" style="display:none"></div><div id="spotlightPlaceholder" style="display:none"></div><div id="graphSettingsContent" style="display: none"></div><div id="filterSelectDiv" style="display:none"></div><div id="offlinePlaceholder" style="display:none"><div class="offline-div"><div class="pure-u"><div class="pure-u-1-4"></div><div class="pure-u-1-2 offline-window"><div class="offline-header"><h3>You have been disconnected from the server</h3></div><div class="offline-body"><p>The connection to the server has been lost. The server may be under heavy load.</p><p>Trying to reconnect in <span id="offlineSeconds">10</span> seconds.</p><p class="animation_state"><span><button class="button-success">Reconnect now</button></span></p></div></div><div class="pure-u-1-4"></div></div></div></div><div class="arangoFrame" style=""><div class="outerDiv"><div class="innerDiv"></div></div></div><script src="libs.js?version=1554822895711"></script><script src="app.js?version=1554822895711"></script></body></html>
|
Binary file not shown.
|
@ -28552,7 +28552,7 @@ var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
|
|||
var AqlHighlightRules = function() {
|
||||
|
||||
var keywords = (
|
||||
"for|return|filter|search|sort|limit|let|collect|asc|desc|in|into|insert|update|remove|replace|upsert|options|with|and|or|not|distinct|graph|shortest_path|outbound|inbound|any|all|none|aggregate|like"
|
||||
"for|return|filter|search|sort|limit|let|collect|asc|desc|in|into|insert|update|remove|replace|upsert|options|with|and|or|not|distinct|graph|shortest_path|outbound|inbound|any|all|none|aggregate|like|k_shortest_paths"
|
||||
);
|
||||
|
||||
var builtinFunctions = (
|
||||
|
|
Binary file not shown.
|
@ -88,7 +88,7 @@ var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
|
|||
var AqlHighlightRules = function() {
|
||||
|
||||
var keywords = (
|
||||
"for|return|filter|search|sort|limit|let|collect|asc|desc|in|into|insert|update|remove|replace|upsert|options|with|and|or|not|distinct|graph|shortest_path|outbound|inbound|any|all|none|aggregate|like"
|
||||
"for|return|filter|search|sort|limit|let|collect|asc|desc|in|into|insert|update|remove|replace|upsert|options|with|and|or|not|distinct|graph|shortest_path|outbound|inbound|any|all|none|aggregate|like|k_shortest_paths"
|
||||
);
|
||||
|
||||
var builtinFunctions = (
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"name": "aardvark",
|
||||
"version": "2.0.0",
|
||||
"description": "The ArangoDB web interface",
|
||||
"author": "Heiko Kernbach, Michael Hackstein, moonglum",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"grunt": "grunt"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.2",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"eslint": "^5.2.0",
|
||||
"eslint-config-semistandard": "^12.0.1",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-plugin-import": "^2.13.0",
|
||||
"eslint-plugin-node": "^7.0.1",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"grunt": "^1.0.3",
|
||||
"grunt-babel": "^8.0.0",
|
||||
"grunt-concat-in-order": "^0.3.0",
|
||||
"grunt-concurrent": "^2.3.1",
|
||||
"grunt-contrib-compress": "^1.4.3",
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
"grunt-contrib-cssmin": "^3.0.0",
|
||||
"grunt-contrib-htmlmin": "^3.0.0",
|
||||
"grunt-contrib-imagemin": "^2.0.1",
|
||||
"grunt-contrib-jshint": "^2.0.0",
|
||||
"grunt-contrib-sass": "^1.0.0",
|
||||
"grunt-contrib-uglify": "^4.0.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-eslint": "^21.0.0",
|
||||
"grunt-processhtml": "^0.4.1",
|
||||
"grunt-sass": "^3.0.2",
|
||||
"grunt-text-replace": "^0.4.0",
|
||||
"load-grunt-tasks": "^4.0.0",
|
||||
"matchdep": "^2.0.0",
|
||||
"node-sass": "^4.9.3",
|
||||
"sass": "^1.10.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/arangodb/arangodb.git"
|
||||
}
|
||||
}
|
|
@ -612,6 +612,69 @@ function printShortestPathDetails(shortestPaths) {
|
|||
}
|
||||
}
|
||||
|
||||
function printKShortestPathsDetails(shortestPaths) {
|
||||
'use strict';
|
||||
if (shortestPaths.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stringBuilder.appendLine();
|
||||
stringBuilder.appendLine(section('k shortest paths on graphs:'));
|
||||
|
||||
var maxIdLen = String('Id').length;
|
||||
var maxVertexCollectionNameStrLen = String('Vertex collections').length;
|
||||
var maxEdgeCollectionNameStrLen = String('Edge collections').length;
|
||||
|
||||
shortestPaths.forEach(function (node) {
|
||||
var l = String(node.id).length;
|
||||
if (l > maxIdLen) {
|
||||
maxIdLen = l;
|
||||
}
|
||||
|
||||
if (node.hasOwnProperty('vertexCollectionNameStr')) {
|
||||
if (node.vertexCollectionNameStrLen > maxVertexCollectionNameStrLen) {
|
||||
maxVertexCollectionNameStrLen = node.vertexCollectionNameStrLen;
|
||||
}
|
||||
}
|
||||
if (node.hasOwnProperty('edgeCollectionNameStr')) {
|
||||
if (node.edgeCollectionNameStrLen > maxEdgeCollectionNameStrLen) {
|
||||
maxEdgeCollectionNameStrLen = node.edgeCollectionNameStrLen;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var line = ' ' + pad(1 + maxIdLen - String('Id').length) + header('Id') + ' ' +
|
||||
header('Vertex collections') + pad(1 + maxVertexCollectionNameStrLen - 'Vertex collections'.length) + ' ' +
|
||||
header('Edge collections') + pad(1 + maxEdgeCollectionNameStrLen - 'Edge collections'.length);
|
||||
|
||||
stringBuilder.appendLine(line);
|
||||
|
||||
for (let sp of shortestPaths) {
|
||||
line = ' ' + pad(1 + maxIdLen - String(sp.id).length) + sp.id + ' ';
|
||||
|
||||
if (sp.hasOwnProperty('vertexCollectionNameStr')) {
|
||||
line += sp.vertexCollectionNameStr +
|
||||
pad(1 + maxVertexCollectionNameStrLen - sp.vertexCollectionNameStrLen) + ' ';
|
||||
} else {
|
||||
line += pad(1 + maxVertexCollectionNameStrLen) + ' ';
|
||||
}
|
||||
|
||||
if (sp.hasOwnProperty('edgeCollectionNameStr')) {
|
||||
line += sp.edgeCollectionNameStr +
|
||||
pad(1 + maxEdgeCollectionNameStrLen - sp.edgeCollectionNameStrLen) + ' ';
|
||||
} else {
|
||||
line += pad(1 + maxEdgeCollectionNameStrLen) + ' ';
|
||||
}
|
||||
|
||||
if (sp.hasOwnProperty('ConditionStr')) {
|
||||
line += sp.ConditionStr;
|
||||
}
|
||||
|
||||
stringBuilder.appendLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* analyze and print execution plan */
|
||||
function processQuery(query, explain, planIndex) {
|
||||
'use strict';
|
||||
|
@ -729,6 +792,7 @@ function processQuery(query, explain, planIndex) {
|
|||
indexes = [],
|
||||
traversalDetails = [],
|
||||
shortestPathDetails = [],
|
||||
kShortestPathsDetails = [],
|
||||
functions = [],
|
||||
modificationFlags,
|
||||
isConst = true,
|
||||
|
@ -1175,7 +1239,7 @@ function processQuery(query, explain, planIndex) {
|
|||
});
|
||||
|
||||
return rc;
|
||||
case 'ShortestPathNode':
|
||||
case 'ShortestPathNode': {
|
||||
if (node.hasOwnProperty('vertexOutVariable')) {
|
||||
parts.push(variableName(node.vertexOutVariable) + ' ' + annotation('/* vertex */'));
|
||||
}
|
||||
|
@ -1183,7 +1247,7 @@ function processQuery(query, explain, planIndex) {
|
|||
parts.push(variableName(node.edgeOutVariable) + ' ' + annotation('/* edge */'));
|
||||
}
|
||||
translate = ['ANY', 'INBOUND', 'OUTBOUND'];
|
||||
var defaultDirection = node.directions[0];
|
||||
let defaultDirection = node.directions[0];
|
||||
rc = `${keyword("FOR")} ${parts.join(", ")} ${keyword("IN")} ${keyword(translate[defaultDirection])} ${keyword("SHORTEST_PATH")} `;
|
||||
if (node.hasOwnProperty('startVertexId')) {
|
||||
rc += `'${value(node.startVertexId)}'`;
|
||||
|
@ -1237,6 +1301,67 @@ function processQuery(query, explain, planIndex) {
|
|||
node.graph = '<anonymous>';
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
case 'KShortestPathsNode': {
|
||||
if (node.hasOwnProperty('pathOutVariable')) {
|
||||
parts.push(variableName(node.pathOutVariable) + ' ' + annotation('/* path */'));
|
||||
}
|
||||
translate = ['ANY', 'INBOUND', 'OUTBOUND'];
|
||||
let defaultDirection = node.directions[0];
|
||||
rc = `${keyword("FOR")} ${parts.join(", ")} ${keyword("IN")} ${keyword(translate[defaultDirection])} ${keyword("K_SHORTEST_PATHS")} `;
|
||||
if (node.hasOwnProperty('startVertexId')) {
|
||||
rc += `'${value(node.startVertexId)}'`;
|
||||
} else {
|
||||
rc += variableName(node.startInVariable);
|
||||
}
|
||||
rc += ` ${annotation("/* startnode */")} ${keyword("TO")} `;
|
||||
|
||||
if (node.hasOwnProperty('targetVertexId')) {
|
||||
rc += `'${value(node.targetVertexId)}'`;
|
||||
} else {
|
||||
rc += variableName(node.targetInVariable);
|
||||
}
|
||||
rc += ` ${annotation("/* targetnode */")} `;
|
||||
|
||||
if (Array.isArray(node.graph)) {
|
||||
rc += node.graph.map(function (g, index) {
|
||||
var tmp = '';
|
||||
if (node.directions[index] !== defaultDirection) {
|
||||
tmp += keyword(translate[node.directions[index]]);
|
||||
tmp += ' ';
|
||||
}
|
||||
return tmp + collection(g);
|
||||
}).join(', ');
|
||||
} else {
|
||||
rc += keyword('GRAPH') + " '" + value(node.graph) + "'";
|
||||
}
|
||||
|
||||
kShortestPathsDetails.push(node);
|
||||
e = [];
|
||||
if (node.hasOwnProperty('graphDefinition')) {
|
||||
v = [];
|
||||
node.graphDefinition.vertexCollectionNames.forEach(function (vcn) {
|
||||
v.push(collection(vcn));
|
||||
});
|
||||
node.vertexCollectionNameStr = v.join(', ');
|
||||
node.vertexCollectionNameStrLen = node.graphDefinition.vertexCollectionNames.join(', ').length;
|
||||
|
||||
node.graphDefinition.edgeCollectionNames.forEach(function (ecn) {
|
||||
e.push(collection(ecn));
|
||||
});
|
||||
node.edgeCollectionNameStr = e.join(', ');
|
||||
node.edgeCollectionNameStrLen = node.graphDefinition.edgeCollectionNames.join(', ').length;
|
||||
} else {
|
||||
edgeCols = node.graph || [];
|
||||
edgeCols.forEach(function (ecn) {
|
||||
e.push(collection(ecn));
|
||||
});
|
||||
node.edgeCollectionNameStr = e.join(', ');
|
||||
node.edgeCollectionNameStrLen = edgeCols.join(', ').length;
|
||||
node.graph = '<anonymous>';
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
case 'CalculationNode':
|
||||
(node.functions || []).forEach(function (f) {
|
||||
functions[f.name] = f;
|
||||
|
@ -1596,6 +1721,7 @@ function processQuery(query, explain, planIndex) {
|
|||
printFunctions(functions);
|
||||
printTraversalDetails(traversalDetails);
|
||||
printShortestPathDetails(shortestPathDetails);
|
||||
printKShortestPathsDetails(kShortestPathsDetails);
|
||||
stringBuilder.appendLine();
|
||||
|
||||
printRules(plan.rules);
|
||||
|
|
|
@ -172,6 +172,7 @@ set(ARANGODB_TESTS_SOURCES
|
|||
Geo/ShapeContainerTest.cpp
|
||||
Graph/ClusterTraverserCacheTest.cpp
|
||||
Graph/ConstantWeightShortestPathFinder.cpp
|
||||
Graph/KShortestPathsFinder.cpp
|
||||
Maintenance/MaintenanceFeatureTest.cpp
|
||||
Maintenance/MaintenanceRestHandlerTest.cpp
|
||||
Maintenance/MaintenanceTest.cpp
|
||||
|
|
|
@ -0,0 +1,454 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2019-2019 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 Markus Pfeiffer
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "catch.hpp"
|
||||
#include "fakeit.hpp"
|
||||
|
||||
#include "Aql/AqlFunctionFeature.h"
|
||||
#include "Aql/Ast.h"
|
||||
#include "Aql/ExecutionPlan.h"
|
||||
#include "Aql/OptimizerRulesFeature.h"
|
||||
#include "Aql/Query.h"
|
||||
#include "ClusterEngine/ClusterEngine.h"
|
||||
#include "Graph/KShortestPathsFinder.h"
|
||||
#include "Graph/ShortestPathOptions.h"
|
||||
#include "Graph/ShortestPathResult.h"
|
||||
#include "Random/RandomGenerator.h"
|
||||
#include "RestServer/AqlFeature.h"
|
||||
#include "RestServer/DatabaseFeature.h"
|
||||
#include "RestServer/DatabasePathFeature.h"
|
||||
#include "RestServer/QueryRegistryFeature.h"
|
||||
#include "RestServer/SystemDatabaseFeature.h"
|
||||
#include "RestServer/TraverserEngineRegistryFeature.h"
|
||||
#include "StorageEngine/EngineSelectorFeature.h"
|
||||
#include "Transaction/Methods.h"
|
||||
#include "Transaction/StandaloneContext.h"
|
||||
#include "Utils/SingleCollectionTransaction.h"
|
||||
#include "VocBase/LogicalCollection.h"
|
||||
|
||||
#include <velocypack/Builder.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
// test setup
|
||||
#include "../Mocks/Servers.h"
|
||||
#include "../Mocks/StorageEngineMock.h"
|
||||
#include "IResearch/common.h"
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::aql;
|
||||
using namespace arangodb::graph;
|
||||
using namespace arangodb::velocypack;
|
||||
|
||||
namespace {
|
||||
struct Setup {
|
||||
StorageEngineMock engine;
|
||||
arangodb::application_features::ApplicationServer server;
|
||||
std::unique_ptr<TRI_vocbase_t> system;
|
||||
std::vector<std::pair<arangodb::application_features::ApplicationFeature*, bool>> features;
|
||||
|
||||
Setup() : engine(server), server(nullptr, nullptr) {
|
||||
arangodb::EngineSelectorFeature::ENGINE = &engine;
|
||||
arangodb::transaction::Methods::clearDataSourceRegistrationCallbacks();
|
||||
arangodb::ClusterEngine::Mocking = true;
|
||||
arangodb::RandomGenerator::initialize(arangodb::RandomGenerator::RandomType::MERSENNE);
|
||||
|
||||
// suppress log messages since tests check error conditions
|
||||
arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(), arangodb::LogLevel::ERR); // suppress WARNING DefaultCustomTypeHandler called
|
||||
|
||||
// setup required application features
|
||||
features.emplace_back(new arangodb::DatabasePathFeature(server), false);
|
||||
features.emplace_back(new arangodb::DatabaseFeature(server), false);
|
||||
features.emplace_back(new arangodb::QueryRegistryFeature(server), false); // must be first
|
||||
arangodb::application_features::ApplicationServer::server->addFeature(
|
||||
features.back().first); // need QueryRegistryFeature feature to be added now in order to create the system database
|
||||
system = std::make_unique<TRI_vocbase_t>(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL,
|
||||
0, TRI_VOC_SYSTEM_DATABASE);
|
||||
features.emplace_back(new arangodb::SystemDatabaseFeature(server, system.get()),
|
||||
false); // required for IResearchAnalyzerFeature
|
||||
features.emplace_back(new arangodb::TraverserEngineRegistryFeature(server), false); // must be before AqlFeature
|
||||
features.emplace_back(new arangodb::AqlFeature(server), true);
|
||||
features.emplace_back(new arangodb::aql::OptimizerRulesFeature(server), true);
|
||||
features.emplace_back(new arangodb::aql::AqlFunctionFeature(server), true); // required for IResearchAnalyzerFeature
|
||||
|
||||
for (auto& f : features) {
|
||||
arangodb::application_features::ApplicationServer::server->addFeature(f.first);
|
||||
}
|
||||
|
||||
for (auto& f : features) {
|
||||
f.first->prepare();
|
||||
}
|
||||
|
||||
for (auto& f : features) {
|
||||
if (f.second) {
|
||||
f.first->start();
|
||||
}
|
||||
}
|
||||
|
||||
auto* dbPathFeature =
|
||||
arangodb::application_features::ApplicationServer::getFeature<arangodb::DatabasePathFeature>(
|
||||
"DatabasePath");
|
||||
arangodb::tests::setDatabasePath(*dbPathFeature); // ensure test data is stored in a unique directory
|
||||
}
|
||||
|
||||
~Setup() {
|
||||
system.reset(); // destroy before reseting the 'ENGINE'
|
||||
arangodb::AqlFeature(server).stop(); // unset singleton instance
|
||||
arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(),
|
||||
arangodb::LogLevel::DEFAULT);
|
||||
arangodb::application_features::ApplicationServer::server = nullptr;
|
||||
arangodb::EngineSelectorFeature::ENGINE = nullptr;
|
||||
|
||||
// destroy application features
|
||||
for (auto& f : features) {
|
||||
if (f.second) {
|
||||
f.first->stop();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& f : features) {
|
||||
f.first->unprepare();
|
||||
}
|
||||
}
|
||||
}; // Setup
|
||||
|
||||
struct MockGraphDatabase {
|
||||
TRI_vocbase_t vocbase;
|
||||
std::vector<arangodb::aql::Query*> queries;
|
||||
std::vector<arangodb::graph::ShortestPathOptions*> spos;
|
||||
|
||||
MockGraphDatabase(std::string name)
|
||||
: vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, name) {}
|
||||
|
||||
~MockGraphDatabase() {
|
||||
for (auto& q : queries) {
|
||||
if (q->trx() != nullptr) {
|
||||
q->trx()->abort();
|
||||
}
|
||||
delete q;
|
||||
}
|
||||
for (auto& o : spos) {
|
||||
delete o;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a collection with name <name> and <n> vertices
|
||||
// with ids 0..n-1
|
||||
void addVertexCollection(std::string name, size_t n) {
|
||||
std::shared_ptr<arangodb::LogicalCollection> vertices;
|
||||
|
||||
auto createJson = velocypack::Parser::fromJson("{ \"name\": \"" + name +
|
||||
"\", \"type\": 2 }");
|
||||
vertices = vocbase.createCollection(createJson->slice());
|
||||
REQUIRE((nullptr != vertices));
|
||||
|
||||
std::vector<velocypack::Builder> insertedDocs;
|
||||
std::vector<std::shared_ptr<arangodb::velocypack::Builder>> docs;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
docs.emplace_back(arangodb::velocypack::Parser::fromJson(
|
||||
"{ \"_key\": \"" + std::to_string(i) + "\"}"));
|
||||
};
|
||||
|
||||
arangodb::OperationOptions options;
|
||||
options.returnNew = true;
|
||||
arangodb::SingleCollectionTransaction trx(arangodb::transaction::StandaloneContext::Create(vocbase),
|
||||
*vertices, arangodb::AccessMode::Type::WRITE);
|
||||
CHECK((trx.begin().ok()));
|
||||
|
||||
for (auto& entry : docs) {
|
||||
auto res = trx.insert(vertices->name(), entry->slice(), options);
|
||||
CHECK((res.ok()));
|
||||
insertedDocs.emplace_back(res.slice().get("new"));
|
||||
}
|
||||
|
||||
CHECK((trx.commit().ok()));
|
||||
CHECK(insertedDocs.size() == n);
|
||||
CHECK(vertices->type());
|
||||
}
|
||||
|
||||
// Create a collection with name <name> of edges given by <edges>
|
||||
void addEdgeCollection(std::string name, std::string vertexCollection,
|
||||
std::vector<std::pair<size_t, size_t>> edgedef) {
|
||||
std::shared_ptr<arangodb::LogicalCollection> edges;
|
||||
auto createJson = velocypack::Parser::fromJson("{ \"name\": \"" + name +
|
||||
"\", \"type\": 3 }");
|
||||
edges = vocbase.createCollection(createJson->slice());
|
||||
REQUIRE((nullptr != edges));
|
||||
|
||||
auto indexJson = velocypack::Parser::fromJson("{ \"type\": \"edge\" }");
|
||||
bool created = false;
|
||||
auto index = edges->createIndex(indexJson->slice(), created);
|
||||
CHECK(index);
|
||||
CHECK(created);
|
||||
|
||||
std::vector<velocypack::Builder> insertedDocs;
|
||||
std::vector<std::shared_ptr<arangodb::velocypack::Builder>> docs;
|
||||
|
||||
for (auto& p : edgedef) {
|
||||
auto docJson = velocypack::Parser::fromJson(
|
||||
"{ \"_from\": \"" + vertexCollection + "/" + std::to_string(p.first) +
|
||||
"\"" + ", \"_to\": \"" + vertexCollection + "/" +
|
||||
std::to_string(p.second) + "\" }");
|
||||
docs.emplace_back(docJson);
|
||||
}
|
||||
|
||||
arangodb::OperationOptions options;
|
||||
options.returnNew = true;
|
||||
arangodb::SingleCollectionTransaction trx(arangodb::transaction::StandaloneContext::Create(vocbase),
|
||||
*edges, arangodb::AccessMode::Type::WRITE);
|
||||
CHECK((trx.begin().ok()));
|
||||
|
||||
for (auto& entry : docs) {
|
||||
auto res = trx.insert(edges->name(), entry->slice(), options);
|
||||
CHECK((res.ok()));
|
||||
insertedDocs.emplace_back(res.slice().get("new"));
|
||||
}
|
||||
|
||||
CHECK((trx.commit().ok()));
|
||||
CHECK(insertedDocs.size() == edgedef.size());
|
||||
}
|
||||
|
||||
arangodb::aql::Query* getQuery(std::string qry) {
|
||||
auto queryString = arangodb::aql::QueryString(qry);
|
||||
|
||||
arangodb::aql::Query* query =
|
||||
new arangodb::aql::Query(false, vocbase, queryString, nullptr,
|
||||
arangodb::velocypack::Parser::fromJson("{}"),
|
||||
arangodb::aql::PART_MAIN);
|
||||
query->parse();
|
||||
query->prepare(arangodb::QueryRegistryFeature::registry());
|
||||
|
||||
queries.emplace_back(query);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
arangodb::graph::ShortestPathOptions* getShortestPathOptions(arangodb::aql::Query* query) {
|
||||
arangodb::graph::ShortestPathOptions* spo;
|
||||
|
||||
auto plan = query->plan();
|
||||
auto ast = plan->getAst();
|
||||
|
||||
auto _toCondition = ast->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND);
|
||||
auto _fromCondition = ast->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND);
|
||||
|
||||
auto tmpVar = plan->getAst()->variables()->createTemporaryVariable();
|
||||
|
||||
AstNode* tmpId1 = plan->getAst()->createNodeReference(tmpVar);
|
||||
AstNode* tmpId2 = plan->getAst()->createNodeValueString("", 0);
|
||||
|
||||
{
|
||||
auto const* access =
|
||||
ast->createNodeAttributeAccess(tmpId1, StaticStrings::ToString.c_str(),
|
||||
StaticStrings::ToString.length());
|
||||
auto const* cond =
|
||||
ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, access, tmpId2);
|
||||
_toCondition->addMember(cond);
|
||||
}
|
||||
|
||||
{
|
||||
auto const* access =
|
||||
ast->createNodeAttributeAccess(tmpId1, StaticStrings::FromString.c_str(),
|
||||
StaticStrings::FromString.length());
|
||||
auto const* cond =
|
||||
ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, access, tmpId2);
|
||||
_fromCondition->addMember(cond);
|
||||
}
|
||||
spo = new ShortestPathOptions(query);
|
||||
spo->setVariable(tmpVar);
|
||||
spo->addLookupInfo(plan, "e", StaticStrings::FromString, _fromCondition->clone(ast));
|
||||
spo->addReverseLookupInfo(plan, "e", StaticStrings::ToString, _toCondition->clone(ast));
|
||||
|
||||
spos.emplace_back(spo);
|
||||
return spo;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace arangodb {
|
||||
namespace tests {
|
||||
namespace graph {
|
||||
|
||||
TEST_CASE("KShortestPathsFinder", "[graph]") {
|
||||
Setup s;
|
||||
UNUSED(s);
|
||||
MockGraphDatabase gdb("testVocbase");
|
||||
|
||||
gdb.addVertexCollection("v", 100);
|
||||
gdb.addEdgeCollection(
|
||||
"e", "v",
|
||||
{{1, 2}, {2, 3}, {3, 4}, {5, 4}, {6, 5}, {7, 6}, {8, 7},
|
||||
{1, 10}, {10, 11}, {11, 12}, {12, 4}, {12, 5}, {21, 22}, {22, 23},
|
||||
{23, 24}, {24, 25}, {21, 26}, {26, 27}, {27, 28}, {28, 25}, {30, 31},
|
||||
{31, 32}, {32, 33}, {33, 34}, {34, 35}, {32, 30}, {33, 35}, {40, 41},
|
||||
{41, 42}, {41, 43}, {42, 44}, {43, 44}, {44, 45}, {45, 46}, {46, 47},
|
||||
{48, 47}, {49, 47}, {50, 47}, {48, 46}, {50, 46}, {50, 47}, {48, 46},
|
||||
{50, 46}, {40, 60}, {60, 61}, {61, 62}, {62, 63}, {63, 64}, {64, 47},
|
||||
{70, 71}, {70, 71}, {70, 71}});
|
||||
|
||||
auto query = gdb.getQuery("RETURN 1");
|
||||
auto spo = gdb.getShortestPathOptions(query);
|
||||
|
||||
auto checkPath = [spo](ShortestPathResult result, std::vector<std::string> vertices,
|
||||
std::vector<std::pair<std::string, std::string>> edges) -> bool {
|
||||
bool res = true;
|
||||
|
||||
if (result.length() != vertices.size()) return false;
|
||||
|
||||
for (size_t i = 0; i < result.length(); i++) {
|
||||
auto vert = result.vertexToAqlValue(spo->cache(), i);
|
||||
if (!vert.slice().get(StaticStrings::KeyString).isEqualString(vertices.at(i))) {
|
||||
LOG_DEVEL << "expected vertex " << vertices.at(i) << " but found "
|
||||
<< vert.slice().get(StaticStrings::KeyString).toString();
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
|
||||
// First edge is by convention null
|
||||
CHECK((result.edgeToAqlValue(spo->cache(), 0).isNull(true)));
|
||||
for (size_t i = 1; i < result.length(); i++) {
|
||||
auto edge = result.edgeToAqlValue(spo->cache(), i);
|
||||
if (!edge.slice().get(StaticStrings::FromString).isEqualString(edges.at(i).first) ||
|
||||
!edge.slice().get(StaticStrings::ToString).isEqualString(edges.at(i).second)) {
|
||||
LOG_DEVEL << "expected edge " << edges.at(i).first << " -> "
|
||||
<< edges.at(i).second << " but found "
|
||||
<< edge.slice().get(StaticStrings::FromString).toString() << " -> "
|
||||
<< edge.slice().get(StaticStrings::ToString).toString();
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
auto finder = new KShortestPathsFinder(*spo);
|
||||
|
||||
|
||||
SECTION("path from vertex to itself") {
|
||||
auto start = velocypack::Parser::fromJson("\"v/0\"");
|
||||
auto end = velocypack::Parser::fromJson("\"v/0\"");
|
||||
ShortestPathResult result;
|
||||
|
||||
finder->startKShortestPathsTraversal(start->slice(), end->slice());
|
||||
|
||||
REQUIRE(true == finder->getNextPathShortestPathResult(result));
|
||||
REQUIRE(false == finder->getNextPathShortestPathResult(result));
|
||||
}
|
||||
|
||||
|
||||
SECTION("no path exists") {
|
||||
auto start = velocypack::Parser::fromJson("\"v/0\"");
|
||||
auto end = velocypack::Parser::fromJson("\"v/1\"");
|
||||
ShortestPathResult result;
|
||||
|
||||
finder->startKShortestPathsTraversal(start->slice(), end->slice());
|
||||
REQUIRE(false == finder->getNextPathShortestPathResult(result));
|
||||
// Repeat to see that we keep returning false and don't crash
|
||||
REQUIRE(false == finder->getNextPathShortestPathResult(result));
|
||||
}
|
||||
|
||||
SECTION("path of length 1") {
|
||||
auto start = velocypack::Parser::fromJson("\"v/1\"");
|
||||
auto end = velocypack::Parser::fromJson("\"v/2\"");
|
||||
ShortestPathResult result;
|
||||
|
||||
finder->startKShortestPathsTraversal(start->slice(), end->slice());
|
||||
|
||||
REQUIRE(true == finder->getNextPathShortestPathResult(result));
|
||||
REQUIRE(checkPath(result, {"1", "2"}, {{}, {"v/1", "v/2"}}));
|
||||
}
|
||||
|
||||
SECTION("path of length 4") {
|
||||
auto start = velocypack::Parser::fromJson("\"v/1\"");
|
||||
auto end = velocypack::Parser::fromJson("\"v/4\"");
|
||||
ShortestPathResult result;
|
||||
|
||||
finder->startKShortestPathsTraversal(start->slice(), end->slice());
|
||||
|
||||
REQUIRE(true == finder->getNextPathShortestPathResult(result));
|
||||
REQUIRE(checkPath(result, {"1", "2", "3", "4"},
|
||||
{{}, {"v/1", "v/2"}, {"v/2", "v/3"}, {"v/3", "v/4"}}));
|
||||
}
|
||||
|
||||
SECTION("path of length 5 with loops to start/end") {
|
||||
auto start = velocypack::Parser::fromJson("\"v/30\"");
|
||||
auto end = velocypack::Parser::fromJson("\"v/35\"");
|
||||
ShortestPathResult result;
|
||||
|
||||
finder->startKShortestPathsTraversal(start->slice(), end->slice());
|
||||
|
||||
REQUIRE(true == finder->getNextPathShortestPathResult(result));
|
||||
REQUIRE(result.length() == 5);
|
||||
}
|
||||
|
||||
SECTION("two paths of length 5") {
|
||||
auto start = velocypack::Parser::fromJson("\"v/21\"");
|
||||
auto end = velocypack::Parser::fromJson("\"v/25\"");
|
||||
ShortestPathResult result;
|
||||
|
||||
finder->startKShortestPathsTraversal(start->slice(), end->slice());
|
||||
|
||||
REQUIRE(true == finder->getNextPathShortestPathResult(result));
|
||||
CHECK((checkPath(result, {"21", "22", "23", "24", "25"},
|
||||
{{},
|
||||
{"v/21", "v/22"},
|
||||
{"v/22", "v/23"},
|
||||
{"v/23", "v/24"},
|
||||
{"v/24", "v/25"}}) ||
|
||||
checkPath(result, {"21", "26", "27", "28", "25"},
|
||||
{{},
|
||||
{"v/21", "v/26"},
|
||||
{"v/26", "v/27"},
|
||||
{"v/27", "v/28"},
|
||||
{"v/28", "v/25"}})));
|
||||
REQUIRE(true == finder->getNextPathShortestPathResult(result));
|
||||
CHECK((checkPath(result, {"21", "22", "23", "24", "25"},
|
||||
{{},
|
||||
{"v/21", "v/22"},
|
||||
{"v/22", "v/23"},
|
||||
{"v/23", "v/24"},
|
||||
{"v/24", "v/25"}}) ||
|
||||
checkPath(result, {"21", "26", "27", "28", "25"},
|
||||
{{},
|
||||
{"v/21", "v/26"},
|
||||
{"v/26", "v/27"},
|
||||
{"v/27", "v/28"},
|
||||
{"v/28", "v/25"}})));
|
||||
}
|
||||
|
||||
SECTION("many edges between two nodes") {
|
||||
auto start = velocypack::Parser::fromJson("\"v/70\"");
|
||||
auto end = velocypack::Parser::fromJson("\"v/71\"");
|
||||
ShortestPathResult result;
|
||||
|
||||
finder->startKShortestPathsTraversal(start->slice(), end->slice());
|
||||
|
||||
REQUIRE(true == finder->getNextPathShortestPathResult(result));
|
||||
REQUIRE(true == finder->getNextPathShortestPathResult(result));
|
||||
REQUIRE(true == finder->getNextPathShortestPathResult(result));
|
||||
REQUIRE(false == finder->getNextPathShortestPathResult(result));
|
||||
}
|
||||
|
||||
delete finder;
|
||||
}
|
||||
} // namespace graph
|
||||
} // namespace tests
|
||||
} // namespace arangodb
|
|
@ -0,0 +1,531 @@
|
|||
/*jshint globalstrict:false, strict:false, sub: true, maxlen: 500 */
|
||||
/*global assertEqual, assertTrue, assertFalse */
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tests for query language, graph functions
|
||||
///
|
||||
/// @file
|
||||
///
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2010-2012 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
|
||||
/// @author Copyright 2018, ArangoDB GmbH, Cologne, Germany
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const jsunity = require("jsunity");
|
||||
const db = require("@arangodb").db;
|
||||
const gm = require("@arangodb/general-graph");
|
||||
const _ = require("underscore");
|
||||
|
||||
const graphName = "UnitTestGraph";
|
||||
const vName = "UnitTestVertices";
|
||||
const e1Name = "UnitTestEdges1";
|
||||
const e2Name = "UnitTestEdges2";
|
||||
const source = `${vName}/source`;
|
||||
const target = `${vName}/target`;
|
||||
const badTarget = `${vName}/badTarget`;
|
||||
const looper = `${vName}/looper`;
|
||||
|
||||
const isPathValid = (path, length, expectedWeight, allowInbound = false) => {
|
||||
assertTrue(_.isObject(path));
|
||||
// Assert all attributes are present
|
||||
assertTrue(path.hasOwnProperty("vertices"));
|
||||
assertTrue(path.hasOwnProperty("edges"));
|
||||
assertTrue(path.hasOwnProperty("weight"));
|
||||
// Assert weight and number of edges are correct
|
||||
const { vertices, edges, weight } = path;
|
||||
assertEqual(edges.length, length);
|
||||
assertEqual(weight, expectedWeight);
|
||||
assertEqual(edges.length + 1, vertices.length);
|
||||
|
||||
// Assert that source and target are correct
|
||||
assertEqual(vertices[0]._id, source);
|
||||
assertEqual(vertices[vertices.length - 1]._id, target);
|
||||
|
||||
// Assert that the edges and vertices are in correct order
|
||||
// And assert that we have not found a vertex twice
|
||||
const seenVertices = new Set();
|
||||
for (let i = 0; i < vertices.length - 1; ++i) {
|
||||
const from = vertices[i]._id;
|
||||
assertFalse(seenVertices.has(from), `Found vertex ${from} twice on path: ${path.vertices.map(v => v._id)}`);
|
||||
seenVertices.add(from);
|
||||
|
||||
const to = vertices[i + 1]._id;
|
||||
// Do not ADD to, it will be added next step, just make sure to test both
|
||||
// for the ends
|
||||
assertFalse(seenVertices.has(to), `Found vertex ${to} twice on path: ${path.vertices.map(v => v._id)}`);
|
||||
const e = edges[i];
|
||||
if (e._from === from) {
|
||||
// OUTBOUND EDGE
|
||||
assertEqual(e._to, to);
|
||||
} else {
|
||||
// INBOUND EDGE
|
||||
assertEqual(e._to, from);
|
||||
assertEqual(e._from, to);
|
||||
assertTrue(allowInbound);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const allPathsDiffer = (paths) => {
|
||||
const seenPath = new Set();
|
||||
for (const p of paths) {
|
||||
const stringP = JSON.stringify(p);
|
||||
assertFalse(seenPath.has(stringP), `Found path ${stringP} twice ${JSON.stringify(paths, null, 2)}`);
|
||||
seenPath.add(stringP);
|
||||
}
|
||||
};
|
||||
|
||||
const allPathsAreSorted = (paths) => {
|
||||
let last = paths[0].weight;
|
||||
for (const p of paths) {
|
||||
assertTrue(last <= p.weight);
|
||||
last = p.weight;
|
||||
}
|
||||
};
|
||||
|
||||
const tearDownAll = () => {
|
||||
try {
|
||||
gm._drop(graphName, true);
|
||||
} catch (e) {
|
||||
// Don't care for error, we might runinitially with no graph exist
|
||||
}
|
||||
db._drop(e2Name);
|
||||
db._drop(e1Name);
|
||||
db._drop(vName);
|
||||
};
|
||||
|
||||
|
||||
const createGraph = () => {
|
||||
gm._create(graphName, [
|
||||
gm._relation(e1Name, vName, vName),
|
||||
gm._relation(e2Name, vName, vName)
|
||||
], [],
|
||||
{
|
||||
numberOfShards: 9
|
||||
}
|
||||
);
|
||||
|
||||
const vertices = [];
|
||||
const e1s = [];
|
||||
const e2s = [];
|
||||
vertices.push({
|
||||
_key: "source"
|
||||
});
|
||||
vertices.push({
|
||||
_key: "target"
|
||||
});
|
||||
vertices.push({
|
||||
_key: "badTarget"
|
||||
});
|
||||
vertices.push({ _key: "looper" });
|
||||
|
||||
// Insert Data:
|
||||
|
||||
// Pathnum 0 - 2 are relevant for result.
|
||||
// Pathnum 3 - 5 are noise on the start vertex
|
||||
// Pathnum 6 - 8 are noise on the target vertex
|
||||
// Pathnum 9 - 11 are noise on the isolated vertex
|
||||
|
||||
for (let pathNum = 0; pathNum < 12; ++pathNum) {
|
||||
const weight = (pathNum + 1) * (pathNum + 1);
|
||||
for (let step = 0; step < 3; ++step) {
|
||||
const key = `vertex_${pathNum}_${step}`;
|
||||
vertices.push({ _key: key });
|
||||
// Add valid edges:
|
||||
switch (step) {
|
||||
case 0: {
|
||||
if (pathNum < 6) {
|
||||
// source -> v
|
||||
e1s.push({ _from: source, _to: `${vName}/${key}`, weight });
|
||||
if (pathNum < 3) {
|
||||
// Add INBOUND shortcut 0 <- 2 in e2 (we intentionally go to path 6-8 to not interfer with the original paths)
|
||||
e2s.push({ _from: `${vName}/vertex_${pathNum + 6}_0`, _to: `${vName}/${key}`, weight });
|
||||
}
|
||||
} else if (pathNum < 9) {
|
||||
// v -> target
|
||||
e1s.push({ _from: `${vName}/${key}`, _to: target, weight });
|
||||
} else {
|
||||
// v-> bad
|
||||
e1s.push({ _from: `${vName}/${key}`, _to: badTarget, weight });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// connect to step 0
|
||||
e1s.push({ _from: `${vName}/vertex_${pathNum}_0`, _to: `${vName}/${key}`, weight });
|
||||
const mod = pathNum % 3;
|
||||
if (mod !== 0) {
|
||||
// Connect to the path before
|
||||
e1s.push({ _from: `${vName}/vertex_${pathNum - 1}_0`, _to: `${vName}/${key}`, weight });
|
||||
}
|
||||
if (mod !== 2) {
|
||||
// Connect to the path after
|
||||
e1s.push({ _from: `${vName}/vertex_${pathNum + 1}_0`, _to: `${vName}/${key}`, weight });
|
||||
}
|
||||
if (mod === 2 && pathNum === 2) {
|
||||
// Add a path loop and a duplicate edge
|
||||
// duplicate edge
|
||||
e1s.push({ _from: `${vName}/vertex_${pathNum}_0`, _to: `${vName}/${key}`, weight: weight + 1 });
|
||||
e1s.push({ _from: `${vName}/${key}`, _to: looper, weight });
|
||||
e1s.push({ _from: looper, _to: `${vName}/vertex_${pathNum}_0`, weight });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
if (pathNum < 3) {
|
||||
// These are the valid paths we care for
|
||||
if (pathNum === 1) {
|
||||
const additional = `vertex_${pathNum}_3`;
|
||||
vertices.push({ _key: additional });
|
||||
// Add an aditional step only on the second path to have differnt path lengths
|
||||
e1s.push({ _from: `${vName}/${key}`, _to: `${vName}/${additional}`, weight });
|
||||
e1s.push({ _from: `${vName}/${additional}`, _to: target, weight });
|
||||
} else {
|
||||
e1s.push({ _from: `${vName}/${key}`, _to: target, weight });
|
||||
}
|
||||
}
|
||||
// Always connect to source:
|
||||
// 1 -> 2 is connected in e2
|
||||
e2s.push({ _from: `${vName}/vertex_${pathNum}_1`, _to: `${vName}/${key}`, weight });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db[vName].save(vertices);
|
||||
db[e1Name].save(e1s);
|
||||
db[e2Name].save(e2s);
|
||||
|
||||
|
||||
// This graph has the following features:
|
||||
// we have 5 paths source -> target of length 4 (via postfix of path0 and path2)
|
||||
// we have 3 paths source -> target of length 5 (via postfix of path1)
|
||||
// we have 1 path source -> target of length 8 (S,V1_0,V2_1,Loop,V2_0,V1_1,V1_2,V1_3,T)
|
||||
// The weights are defined as (1 + pathNum)^2 on every edge (the duplicate on path2 is 10 instead of 9).
|
||||
// So we end up with weight on the following paths:
|
||||
|
||||
// * 4 on path0 (4 edges)
|
||||
// * 7 path1->path0 (4 edges)
|
||||
// * 17 path0->path1 (5 edges)
|
||||
// * 20 path1 (5edges)
|
||||
// * 25 path2->path1 (5 edges)
|
||||
// * 31 path1->path2 (4 edges)
|
||||
// * 36 path2 (4 edges)
|
||||
// * 37 path2 alt(4 edges)
|
||||
// * 47 on loop path (8 edges)
|
||||
|
||||
|
||||
// We hav no paths source -> badTarget
|
||||
// We have 4 paths when e2 is traversed inbound instead of outbound
|
||||
// source -e1> <e2- target of length 3
|
||||
// source -e1> loop -e1> 0_2 <e2- t of length 6
|
||||
|
||||
// So we end up with weight on the following paths:
|
||||
// * 51 on path0
|
||||
// * 72 on path1
|
||||
// * 99 on path2
|
||||
// * 121 on loop
|
||||
};
|
||||
|
||||
function kConstantWeightShortestPathTestSuite() {
|
||||
return {
|
||||
setUpAll: function () {
|
||||
tearDownAll();
|
||||
createGraph();
|
||||
},
|
||||
tearDownAll,
|
||||
|
||||
testPathsExistsLimit: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" GRAPH "${graphName}"
|
||||
LIMIT 6
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 6);
|
||||
allPathsAreSorted(result);
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
isPathValid(result[i], 4, 4);
|
||||
}
|
||||
// The 6th path is the only longer one
|
||||
isPathValid(result[5], 5, 5);
|
||||
|
||||
|
||||
},
|
||||
|
||||
testNoPathExistsLimit: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${badTarget}" GRAPH "${graphName}"
|
||||
LIMIT 6
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
assertEqual(result.length, 0);
|
||||
},
|
||||
|
||||
testFewerPathsThanLimit: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" GRAPH "${graphName}"
|
||||
LIMIT 1000
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 9);
|
||||
allPathsAreSorted(result);
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
isPathValid(result[i], 4, 4);
|
||||
}
|
||||
for (let i = 5; i < 8; ++i) {
|
||||
isPathValid(result[i], 5, 5);
|
||||
}
|
||||
isPathValid(result[8], 8, 8);
|
||||
},
|
||||
|
||||
testPathsExistsNoLimit: function () {
|
||||
const query = `
|
||||
FOR source IN ${vName}
|
||||
FILTER source._id == "${source}"
|
||||
FOR target IN ${vName}
|
||||
FILTER target._id == "${target}"
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS source TO target GRAPH "${graphName}"
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 9);
|
||||
allPathsAreSorted(result);
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
isPathValid(result[i], 4, 4);
|
||||
}
|
||||
for (let i = 5; i < 8; ++i) {
|
||||
isPathValid(result[i], 5, 5);
|
||||
}
|
||||
isPathValid(result[8], 8, 8);
|
||||
},
|
||||
|
||||
testNoPathsExistsNoLimit: function () {
|
||||
const query = `
|
||||
FOR source IN ${vName}
|
||||
FILTER source._id == "${source}"
|
||||
FOR target IN ${vName}
|
||||
FILTER target._id == "${badTarget}"
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS source TO target GRAPH "${graphName}"
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
assertEqual(result.length, 0);
|
||||
},
|
||||
|
||||
testPathsSkip: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" GRAPH "${graphName}"
|
||||
LIMIT 3, 3
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 3);
|
||||
allPathsAreSorted(result);
|
||||
|
||||
for (let i = 0; i < 2; ++i) {
|
||||
isPathValid(result[i], 4, 4);
|
||||
}
|
||||
for (let i = 2; i < 3; ++i) {
|
||||
isPathValid(result[i], 5, 5);
|
||||
}
|
||||
},
|
||||
|
||||
testPathsSkipMoreThanExists: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" GRAPH "${graphName}"
|
||||
LIMIT 1000, 2
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
assertEqual(result.length, 0);
|
||||
},
|
||||
|
||||
testMultiDirections: function () {
|
||||
const query = `
|
||||
WITH ${vName}
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" ${e1Name}, INBOUND ${e2Name}
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 4);
|
||||
allPathsAreSorted(result);
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
isPathValid(result[i], 3, 3, true);
|
||||
}
|
||||
isPathValid(result[3], 6, 6, true);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function kAttributeWeightShortestPathTestSuite() {
|
||||
return {
|
||||
setUpAll: function () {
|
||||
tearDownAll();
|
||||
createGraph();
|
||||
},
|
||||
tearDownAll,
|
||||
|
||||
testWeightPathsExistsLimit: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" GRAPH "${graphName}" OPTIONS {weightAttribute: "weight"}
|
||||
LIMIT 6
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 6);
|
||||
allPathsAreSorted(result);
|
||||
isPathValid(result[0], 4, 4);
|
||||
isPathValid(result[1], 4, 7);
|
||||
isPathValid(result[2], 5, 17);
|
||||
isPathValid(result[3], 5, 20);
|
||||
isPathValid(result[4], 5, 25);
|
||||
isPathValid(result[5], 4, 31);
|
||||
},
|
||||
|
||||
testWeightNoPathExistsLimit: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${badTarget}" GRAPH "${graphName}" OPTIONS {weightAttribute: "weight"}
|
||||
LIMIT 6
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
assertEqual(result.length, 0);
|
||||
},
|
||||
|
||||
testWeightFewerPathsThanLimit: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" GRAPH "${graphName}" OPTIONS {weightAttribute: "weight"}
|
||||
LIMIT 1000
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 9);
|
||||
allPathsAreSorted(result);
|
||||
isPathValid(result[0], 4, 4);
|
||||
isPathValid(result[1], 4, 7);
|
||||
isPathValid(result[2], 5, 17);
|
||||
isPathValid(result[3], 5, 20);
|
||||
isPathValid(result[4], 5, 25);
|
||||
isPathValid(result[5], 4, 31);
|
||||
isPathValid(result[6], 4, 36);
|
||||
isPathValid(result[7], 4, 37);
|
||||
isPathValid(result[8], 8, 47);
|
||||
},
|
||||
|
||||
testWeightPathsExistsNoLimit: function () {
|
||||
const query = `
|
||||
FOR source IN ${vName}
|
||||
FILTER source._id == "${source}"
|
||||
FOR target IN ${vName}
|
||||
FILTER target._id == "${target}"
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS source TO target GRAPH "${graphName}" OPTIONS {weightAttribute: "weight"}
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 9);
|
||||
allPathsAreSorted(result);
|
||||
isPathValid(result[0], 4, 4);
|
||||
isPathValid(result[1], 4, 7);
|
||||
isPathValid(result[2], 5, 17);
|
||||
isPathValid(result[3], 5, 20);
|
||||
isPathValid(result[4], 5, 25);
|
||||
isPathValid(result[5], 4, 31);
|
||||
isPathValid(result[6], 4, 36);
|
||||
isPathValid(result[7], 4, 37);
|
||||
isPathValid(result[8], 8, 47);
|
||||
},
|
||||
|
||||
testWeightNoPathsExistsNoLimit: function () {
|
||||
const query = `
|
||||
FOR source IN ${vName}
|
||||
FILTER source._id == "${source}"
|
||||
FOR target IN ${vName}
|
||||
FILTER target._id == "${badTarget}"
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS source TO target GRAPH "${graphName}" OPTIONS {weightAttribute: "weight"}
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
assertEqual(result.length, 0);
|
||||
},
|
||||
|
||||
testWeightPathsSkip: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" GRAPH "${graphName}" OPTIONS {weightAttribute: "weight"}
|
||||
LIMIT 3, 3
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 3);
|
||||
allPathsAreSorted(result);
|
||||
isPathValid(result[0], 5, 20);
|
||||
isPathValid(result[1], 5, 25);
|
||||
isPathValid(result[2], 4, 31);
|
||||
},
|
||||
|
||||
testWeightPathsSkipMoreThanExists: function () {
|
||||
const query = `
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" GRAPH "${graphName}" OPTIONS {weightAttribute: "weight"}
|
||||
LIMIT 1000, 2
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
assertEqual(result.length, 0);
|
||||
},
|
||||
|
||||
testWeightMultiDirections: function () {
|
||||
const query = `
|
||||
WITH ${vName}
|
||||
FOR path IN OUTBOUND K_SHORTEST_PATHS "${source}" TO "${target}" ${e1Name}, INBOUND ${e2Name} OPTIONS {weightAttribute: "weight"}
|
||||
RETURN path
|
||||
`;
|
||||
const result = db._query(query).toArray();
|
||||
allPathsDiffer(result);
|
||||
assertEqual(result.length, 4);
|
||||
allPathsAreSorted(result);
|
||||
isPathValid(result[0], 3, 51, true);
|
||||
isPathValid(result[1], 3, 72, true);
|
||||
isPathValid(result[2], 3, 99, true);
|
||||
isPathValid(result[3], 6, 121, true);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
jsunity.run(kConstantWeightShortestPathTestSuite);
|
||||
jsunity.run(kAttributeWeightShortestPathTestSuite);
|
||||
|
||||
|
||||
return jsunity.done();
|
Loading…
Reference in New Issue