1
0
Fork 0
arangodb/arangod/Graph/GraphManager.cpp

988 lines
33 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2018 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Heiko Kernbach
////////////////////////////////////////////////////////////////////////////////
#include "GraphManager.h"
#include "GraphOperations.h"
#include <velocypack/Buffer.h>
#include <velocypack/Collection.h>
#include <velocypack/Iterator.h>
#include <velocypack/velocypack-aliases.h>
#include <array>
#include <boost/range/join.hpp>
#include <boost/variant.hpp>
#include <utility>
#include "Aql/AstNode.h"
#include "Aql/Graphs.h"
#include "Aql/Query.h"
#include "Basics/ReadLocker.h"
#include "Basics/StaticStrings.h"
#include "Basics/VelocyPackHelper.h"
#include "Basics/WriteLocker.h"
#include "Cluster/ClusterInfo.h"
#include "Cluster/ServerState.h"
#include "Graph/Graph.h"
#include "RestServer/QueryRegistryFeature.h"
#include "Sharding/ShardingInfo.h"
#include "Transaction/Methods.h"
#include "Transaction/StandaloneContext.h"
#include "Transaction/V8Context.h"
#include "Utils/ExecContext.h"
#include "Utils/OperationOptions.h"
#include "Utils/SingleCollectionTransaction.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/Methods/CollectionCreationInfo.h"
#include "VocBase/Methods/Collections.h"
using namespace arangodb;
using namespace arangodb::graph;
using VelocyPackHelper = basics::VelocyPackHelper;
namespace {
static bool ArrayContainsCollection(VPackSlice array, std::string const& colName) {
TRI_ASSERT(array.isArray());
for (auto const& it : VPackArrayIterator(array)) {
if (it.copyString() == colName) {
return true;
}
}
return false;
}
} // namespace
std::shared_ptr<transaction::Context> GraphManager::ctx() const {
if (_isInTransaction) {
// we must use v8
return transaction::V8Context::Create(_vocbase, true);
}
return transaction::StandaloneContext::Create(_vocbase);
};
OperationResult GraphManager::createEdgeCollection(std::string const& name,
bool waitForSync, VPackSlice options) {
return createCollection(name, TRI_COL_TYPE_EDGE, waitForSync, options);
}
OperationResult GraphManager::createVertexCollection(std::string const& name, bool waitForSync,
VPackSlice options) {
return createCollection(name, TRI_COL_TYPE_DOCUMENT, waitForSync, options);
}
OperationResult GraphManager::createCollection(std::string const& name, TRI_col_type_e colType,
bool waitForSync, VPackSlice options) {
TRI_ASSERT(colType == TRI_COL_TYPE_DOCUMENT || colType == TRI_COL_TYPE_EDGE);
if (ServerState::instance()->isCoordinator()) {
Result res = ShardingInfo::validateShardsAndReplicationFactor(options);
if (res.fail()) {
return OperationResult(res);
}
}
auto res = arangodb::methods::Collections::create( // create collection
ctx()->vocbase(), // collection vocbase
name, // collection name
colType, // collection type
options, // collection properties
waitForSync, true, false, [](std::shared_ptr<LogicalCollection> const&) -> void {});
return OperationResult(res);
}
OperationResult GraphManager::findOrCreateVertexCollectionByName(const std::string& name,
bool waitForSync,
VPackSlice options) {
std::shared_ptr<LogicalCollection> def;
def = getCollectionByName(ctx()->vocbase(), name);
if (def == nullptr) {
return createVertexCollection(name, waitForSync, options);
}
return OperationResult(TRI_ERROR_NO_ERROR);
}
bool GraphManager::renameGraphCollection(std::string const& oldName,
std::string const& newName) {
// todo: return a result, by now just used in the graph modules
std::vector<std::unique_ptr<Graph>> renamedGraphs;
auto callback = [&](std::unique_ptr<Graph> graph) -> Result {
bool renamed = graph->renameCollections(oldName, newName);
if (renamed) {
renamedGraphs.emplace_back(std::move(graph));
}
return Result{};
};
Result res = applyOnAllGraphs(callback);
if (res.fail()) {
return false;
}
SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection,
AccessMode::Type::WRITE);
res = trx.begin();
if (!res.ok()) {
return false;
}
OperationOptions options;
OperationResult checkDoc;
for (auto const& graph : renamedGraphs) {
VPackBuilder builder;
builder.openObject();
graph->toPersistence(builder);
builder.close();
try {
OperationResult checkDoc =
trx.update(StaticStrings::GraphCollection, builder.slice(), options);
if (checkDoc.fail()) {
res = trx.finish(checkDoc.result);
if (res.fail()) {
return false;
}
}
} catch (...) {
}
};
res = trx.finish(checkDoc.result);
if (res.fail()) {
return false;
}
return true;
}
Result GraphManager::checkForEdgeDefinitionConflicts(std::map<std::string, EdgeDefinition> const& edgeDefinitions,
std::string const& graphName) const {
auto callback = [&](std::unique_ptr<Graph> graph) -> Result {
if (graph->name() == graphName) {
// No need to check our graph
return {TRI_ERROR_NO_ERROR};
}
for (auto const& sGED : graph->edgeDefinitions()) {
std::string const& col = sGED.first;
auto it = edgeDefinitions.find(col);
if (it != edgeDefinitions.end()) {
if (sGED.second != it->second) {
// found an incompatible edge definition for the same collection
return Result(TRI_ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS,
sGED.first + " " +
std::string{TRI_errno_string(
TRI_ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS)});
}
}
}
return {TRI_ERROR_NO_ERROR};
};
return applyOnAllGraphs(callback);
}
OperationResult GraphManager::findOrCreateCollectionsByEdgeDefinitions(
std::map<std::string, EdgeDefinition> const& edgeDefinitions,
bool waitForSync, VPackSlice options) {
for (auto const& it : edgeDefinitions) {
EdgeDefinition const& edgeDefinition = it.second;
OperationResult res =
findOrCreateCollectionsByEdgeDefinition(edgeDefinition, waitForSync, options);
if (res.fail()) {
return res;
}
}
return OperationResult{TRI_ERROR_NO_ERROR};
}
OperationResult GraphManager::findOrCreateCollectionsByEdgeDefinition(
EdgeDefinition const& edgeDefinition, bool waitForSync, VPackSlice const options) {
std::string const& edgeCollection = edgeDefinition.getName();
std::shared_ptr<LogicalCollection> def =
getCollectionByName(ctx()->vocbase(), edgeCollection);
if (def == nullptr) {
OperationResult res = createEdgeCollection(edgeCollection, waitForSync, options);
if (res.fail()) {
return res;
}
}
std::unordered_set<std::string> vertexCollections;
// duplicates in from and to shouldn't occur, but are safely ignored here
for (auto const& colName : edgeDefinition.getFrom()) {
vertexCollections.emplace(colName);
}
for (auto const& colName : edgeDefinition.getTo()) {
vertexCollections.emplace(colName);
}
for (auto const& colName : vertexCollections) {
def = getCollectionByName(ctx()->vocbase(), colName);
if (def == nullptr) {
OperationResult res = createVertexCollection(colName, waitForSync, options);
if (res.fail()) {
return res;
}
}
}
return OperationResult{TRI_ERROR_NO_ERROR};
}
/// @brief extract the collection by either id or name, may return nullptr!
std::shared_ptr<LogicalCollection> GraphManager::getCollectionByName(
const TRI_vocbase_t& vocbase, std::string const& name) {
if (!name.empty()) {
// try looking up the collection by name then
if (arangodb::ServerState::instance()->isRunningInCluster()) {
ClusterInfo* ci = ClusterInfo::instance();
return ci->getCollectionNT(vocbase.name(), name);
} else {
return vocbase.lookupCollection(name);
}
}
return nullptr;
}
bool GraphManager::graphExists(std::string const& graphName) const {
VPackBuilder checkDocument;
{
VPackObjectBuilder guard(&checkDocument);
checkDocument.add(StaticStrings::KeyString, VPackValue(graphName));
}
SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection,
AccessMode::Type::READ);
trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION);
Result res = trx.begin();
if (!res.ok()) {
return false;
}
OperationOptions options;
try {
OperationResult checkDoc =
trx.document(StaticStrings::GraphCollection, checkDocument.slice(), options);
if (checkDoc.fail()) {
trx.finish(checkDoc.result);
return false;
}
trx.finish(checkDoc.result);
} catch (...) {
}
return true;
}
ResultT<std::unique_ptr<Graph>> GraphManager::lookupGraphByName(std::string const& name) const {
SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection,
AccessMode::Type::READ);
Result res = trx.begin();
if (res.fail()) {
std::stringstream ss;
ss << "while looking up graph '" << name << "': " << res.errorMessage();
res.reset(res.errorNumber(), ss.str());
return {res};
}
VPackBuilder b;
{
VPackObjectBuilder guard(&b);
b.add(StaticStrings::KeyString, VPackValue(name));
}
// Default options are enough here
OperationOptions options;
OperationResult result = trx.document(StaticStrings::GraphCollection, b.slice(), options);
// Commit or abort.
res = trx.finish(result.result);
if (result.fail()) {
if (result.errorNumber() == TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND) {
std::string msg = basics::Exception::FillExceptionString(TRI_ERROR_GRAPH_NOT_FOUND,
name.c_str());
return Result{TRI_ERROR_GRAPH_NOT_FOUND, std::move(msg)};
} else {
return Result{result.errorNumber(),
"while looking up graph '" + name + "'"};
}
}
if (res.fail()) {
std::stringstream ss;
ss << "while looking up graph '" << name << "': " << res.errorMessage();
res.reset(res.errorNumber(), ss.str());
return {res};
}
return {Graph::fromPersistence(result.slice(), _vocbase)};
}
OperationResult GraphManager::createGraph(VPackSlice document, bool waitForSync) const {
VPackSlice graphNameSlice = document.get("name");
if (!graphNameSlice.isString()) {
return OperationResult{TRI_ERROR_GRAPH_CREATE_MISSING_NAME};
}
std::string const graphName = graphNameSlice.copyString();
if (graphExists(graphName)) {
return OperationResult{TRI_ERROR_GRAPH_DUPLICATE};
}
auto graphRes = buildGraphFromInput(graphName, document);
if (graphRes.fail()) {
return OperationResult{std::move(graphRes).result()};
}
// Guaranteed to not be nullptr
std::unique_ptr<Graph> graph = std::move(graphRes.get());
TRI_ASSERT(graph != nullptr);
// check permissions
Result res = checkCreateGraphPermissions(graph.get());
if (res.fail()) {
return OperationResult{res};
}
// check edgeDefinitionConflicts
res = checkForEdgeDefinitionConflicts(graph->edgeDefinitions(), graph->name());
if (res.fail()) {
return OperationResult{res};
}
// Make sure all collections exist and are created
res = ensureCollections(graph.get(), waitForSync);
if (res.fail()) {
return OperationResult{res};
}
// finally save the graph
return storeGraph(*(graph.get()), waitForSync, false);
}
OperationResult GraphManager::storeGraph(Graph const& graph, bool waitForSync,
bool isUpdate) const {
VPackBuilder builder;
builder.openObject();
graph.toPersistence(builder);
builder.close();
// Here we need a second transaction.
// If now someone has created a graph with the same name
// in the meanwhile, sorry bad luck.
SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection,
AccessMode::Type::WRITE);
trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION);
OperationOptions options;
options.waitForSync = waitForSync;
Result res = trx.begin();
if (res.fail()) {
return OperationResult{std::move(res)};
}
OperationResult result;
if (isUpdate) {
result = trx.update(StaticStrings::GraphCollection, builder.slice(), options);
} else {
result = trx.insert(StaticStrings::GraphCollection, builder.slice(), options);
}
if (!result.ok()) {
trx.finish(result.result);
return result;
}
res = trx.finish(result.result);
if (res.fail()) {
return OperationResult{std::move(res)};
}
return result;
}
Result GraphManager::applyOnAllGraphs(std::function<Result(std::unique_ptr<Graph>)> const& callback) const {
std::string const queryStr{"FOR g IN _graphs RETURN g"};
arangodb::aql::Query query(false, _vocbase, arangodb::aql::QueryString{"FOR g IN _graphs RETURN g"},
nullptr, nullptr, aql::PART_MAIN);
aql::QueryResult queryResult = query.executeSync(QueryRegistryFeature::registry());
if (queryResult.result.fail()) {
if (queryResult.result.is(TRI_ERROR_REQUEST_CANCELED) ||
(queryResult.result.is(TRI_ERROR_QUERY_KILLED))) {
return {TRI_ERROR_REQUEST_CANCELED};
}
return queryResult.result;
}
VPackSlice graphsSlice = queryResult.data->slice();
if (graphsSlice.isNone()) {
return {TRI_ERROR_OUT_OF_MEMORY};
} else if (!graphsSlice.isArray()) {
LOG_TOPIC("cbe2c", ERR, arangodb::Logger::GRAPHS)
<< "cannot read graphs from _graphs collection";
return {TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT,
"Cannot read graphs from _graphs collection"};
}
Result res;
for (auto const& it : VPackArrayIterator(graphsSlice)) {
std::unique_ptr<Graph> graph;
try {
graph = Graph::fromPersistence(it.resolveExternals(), _vocbase);
} catch (basics::Exception const& e) {
return {e.code(), e.message()};
}
TRI_ASSERT(graph != nullptr);
if (graph == nullptr) {
return {TRI_ERROR_OUT_OF_MEMORY};
}
res = callback(std::move(graph));
if (res.fail()) {
return res;
}
}
TRI_ASSERT(res.ok());
return res;
}
Result GraphManager::ensureCollections(Graph const* graph, bool waitForSync) const {
// Validation Phase collect a list of collections to create
std::unordered_set<std::string> documentCollectionsToCreate{};
std::unordered_set<std::string> edgeCollectionsToCreate{};
auto& vocbase = ctx()->vocbase();
Result innerRes{TRI_ERROR_NO_ERROR};
// Check that all edgeCollections are either to be created
// or exist in a valid way.
for (auto const& edgeColl : graph->edgeCollections()) {
bool found = false;
Result res = methods::Collections::lookup(
vocbase, edgeColl,
[&found, &innerRes, &graph](std::shared_ptr<LogicalCollection> const& col) -> void {
TRI_ASSERT(col);
if (col->type() != TRI_COL_TYPE_EDGE) {
innerRes.reset(TRI_ERROR_GRAPH_EDGE_DEFINITION_IS_DOCUMENT,
"Collection: '" + col->name() +
"' is not an EdgeCollection");
} else {
innerRes = graph->validateCollection(*col);
found = true;
}
});
if (innerRes.fail()) {
return innerRes;
}
// Check if we got an error other then CollectionNotFound
if (res.fail() && !res.is(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND)) {
return res;
}
if (!found) {
edgeCollectionsToCreate.emplace(edgeColl);
}
}
// Check that all vertexCollections are either to be created
// or exist in a valid way.
// If there is an edge collection used as a vertex collection
// it will not be created twice
for (auto const& vertexColl : graph->vertexCollections()) {
bool found = false;
Result res = methods::Collections::lookup(
vocbase, vertexColl,
[&found, &innerRes, &graph](std::shared_ptr<LogicalCollection> const& col) -> void {
TRI_ASSERT(col);
innerRes = graph->validateCollection(*col);
found = true;
});
if (innerRes.fail()) {
return innerRes;
}
// Check if we got an error other then CollectionNotFound
if (res.fail() && !res.is(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND)) {
return res;
}
if (!found) {
if (edgeCollectionsToCreate.find(vertexColl) == edgeCollectionsToCreate.end()) {
documentCollectionsToCreate.emplace(vertexColl);
}
}
}
#ifdef USE_ENTERPRISE
{
Result res = ensureSmartCollectionSharding(graph, waitForSync, documentCollectionsToCreate);
if (res.fail()) {
return res;
}
}
#endif
VPackBuilder optionsBuilder;
optionsBuilder.openObject();
graph->createCollectionOptions(optionsBuilder, waitForSync);
optionsBuilder.close();
VPackSlice options = optionsBuilder.slice();
std::vector<CollectionCreationInfo> collectionsToCreate;
collectionsToCreate.reserve(documentCollectionsToCreate.size() +
edgeCollectionsToCreate.size());
// Create Document Collections
for (auto const& vertexColl : documentCollectionsToCreate) {
collectionsToCreate.emplace_back(
CollectionCreationInfo{vertexColl, TRI_COL_TYPE_DOCUMENT, options});
}
// Create Edge Collections
for (auto const& edgeColl : edgeCollectionsToCreate) {
collectionsToCreate.emplace_back(
CollectionCreationInfo{edgeColl, TRI_COL_TYPE_EDGE, options});
}
if (collectionsToCreate.empty()) {
// NOTE: Empty graph is allowed.
return TRI_ERROR_NO_ERROR;
}
return methods::Collections::create(
vocbase, collectionsToCreate, waitForSync, true, false, nullptr,
[](std::vector<std::shared_ptr<LogicalCollection>> const&) -> void {});
};
OperationResult GraphManager::readGraphs(velocypack::Builder& builder,
aql::QueryPart const queryPart) const {
std::string const queryStr{
"FOR g IN _graphs RETURN MERGE(g, {name: g._key})"};
return readGraphByQuery(builder, queryPart, queryStr);
}
OperationResult GraphManager::readGraphKeys(velocypack::Builder& builder,
aql::QueryPart const queryPart) const {
std::string const queryStr{"FOR g IN _graphs RETURN g._key"};
return readGraphByQuery(builder, queryPart, queryStr);
}
OperationResult GraphManager::readGraphByQuery(velocypack::Builder& builder,
aql::QueryPart const queryPart,
std::string queryStr) const {
arangodb::aql::Query query(false, ctx()->vocbase(), arangodb::aql::QueryString(queryStr),
nullptr, nullptr, queryPart);
LOG_TOPIC("f6782", DEBUG, arangodb::Logger::GRAPHS)
<< "starting to load graphs information";
aql::QueryResult queryResult = query.executeSync(QueryRegistryFeature::registry());
if (queryResult.result.fail()) {
if (queryResult.result.is(TRI_ERROR_REQUEST_CANCELED) ||
(queryResult.result.is(TRI_ERROR_QUERY_KILLED))) {
return OperationResult(TRI_ERROR_REQUEST_CANCELED);
}
return OperationResult(std::move(queryResult.result));
}
VPackSlice graphsSlice = queryResult.data->slice();
if (graphsSlice.isNone()) {
return OperationResult(TRI_ERROR_OUT_OF_MEMORY);
} else if (!graphsSlice.isArray()) {
LOG_TOPIC("338b7", ERR, arangodb::Logger::GRAPHS)
<< "cannot read graphs from _graphs collection";
}
builder.add(VPackValue(VPackValueType::Object));
builder.add("graphs", graphsSlice);
builder.close();
return OperationResult(TRI_ERROR_NO_ERROR);
}
Result GraphManager::checkForEdgeDefinitionConflicts(arangodb::graph::EdgeDefinition const& edgeDefinition,
std::string const& graphName) const {
std::map<std::string, EdgeDefinition> edgeDefs{
std::make_pair(edgeDefinition.getName(), edgeDefinition)};
return checkForEdgeDefinitionConflicts(edgeDefs, graphName);
}
Result GraphManager::checkCreateGraphPermissions(Graph const* graph) const {
std::string const& databaseName = ctx()->vocbase().name();
std::stringstream stringstream;
stringstream << "When creating graph " << databaseName << "." << graph->name() << ": ";
std::string const logprefix = stringstream.str();
ExecContext const* execContext = ExecContext::CURRENT;
if (execContext == nullptr) {
LOG_TOPIC("952c0", DEBUG, Logger::GRAPHS) << logprefix << "Permissions are turned off.";
return TRI_ERROR_NO_ERROR;
}
// Test if we are allowed to modify _graphs first.
// Note that this check includes the following check in the loop
// if (!collectionExists(col) && !canUseDatabaseRW)
// as canUseDatabase(RW) <=> canUseCollection("_...", RW).
// However, in case a collection has to be created but can't, we have to throw
// FORBIDDEN instead of READ_ONLY for backwards compatibility.
if (!execContext->canUseDatabase(auth::Level::RW)) {
// Check for all collections: if it exists and if we have RO access to it.
// If none fails the check above we need to return READ_ONLY.
// Otherwise we return FORBIDDEN
auto checkCollectionAccess = [&](std::string const& col) -> bool {
// We need RO on all collections. And, in case any collection does not
// exist, we need RW on the database.
if (!collectionExists(col)) {
LOG_TOPIC("ca4de", DEBUG, Logger::GRAPHS)
<< logprefix << "Cannot create collection " << databaseName << "." << col;
return false;
}
if (!execContext->canUseCollection(col, auth::Level::RO)) {
LOG_TOPIC("b4d48", DEBUG, Logger::GRAPHS)
<< logprefix << "No read access to " << databaseName << "." << col;
return false;
}
return true;
};
// Test all edge Collections
for (auto const& it : graph->edgeCollections()) {
if (!checkCollectionAccess(it)) {
return {TRI_ERROR_FORBIDDEN,
"Createing Graphs requires RW access on the database (" +
databaseName + ")"};
}
}
// Test all vertex Collections
for (auto const& it : graph->vertexCollections()) {
if (!checkCollectionAccess(it)) {
return {TRI_ERROR_FORBIDDEN,
"Createing Graphs requires RW access on the database (" +
databaseName + ")"};
}
}
LOG_TOPIC("89b89", DEBUG, Logger::GRAPHS)
<< logprefix << "No write access to " << databaseName << "."
<< StaticStrings::GraphCollection;
return {TRI_ERROR_ARANGO_READ_ONLY,
"Createing Graphs requires RW access on the database (" +
databaseName + ")"};
}
auto checkCollectionAccess = [&](std::string const& col) -> bool {
// We need RO on all collections. And, in case any collection does not
// exist, we need RW on the database.
if (!execContext->canUseCollection(col, auth::Level::RO)) {
LOG_TOPIC("43c84", DEBUG, Logger::GRAPHS)
<< logprefix << "No read access to " << databaseName << "." << col;
return false;
}
return true;
};
// Test all edge Collections
for (auto const& it : graph->edgeCollections()) {
if (!checkCollectionAccess(it)) {
return TRI_ERROR_FORBIDDEN;
}
}
// Test all vertex Collections
for (auto const& it : graph->vertexCollections()) {
if (!checkCollectionAccess(it)) {
return TRI_ERROR_FORBIDDEN;
}
}
return TRI_ERROR_NO_ERROR;
}
bool GraphManager::collectionExists(std::string const& collection) const {
return getCollectionByName(ctx()->vocbase(), collection) != nullptr;
}
OperationResult GraphManager::removeGraph(Graph const& graph, bool waitForSync,
bool dropCollections) {
std::unordered_set<std::string> leadersToBeRemoved;
std::unordered_set<std::string> followersToBeRemoved;
if (dropCollections) {
auto addToRemoveCollections = [this, &graph, &leadersToBeRemoved,
&followersToBeRemoved](std::string const& colName) {
std::shared_ptr<LogicalCollection> col =
getCollectionByName(ctx()->vocbase(), colName);
if (col == nullptr) {
return;
}
if (col->distributeShardsLike().empty()) {
pushCollectionIfMayBeDropped(colName, graph.name(), leadersToBeRemoved);
} else {
pushCollectionIfMayBeDropped(colName, graph.name(), followersToBeRemoved);
}
};
for (auto const& vertexCollection : graph.vertexCollections()) {
addToRemoveCollections(vertexCollection);
}
for (auto const& orphanCollection : graph.orphanCollections()) {
addToRemoveCollections(orphanCollection);
}
for (auto const& edgeCollection : graph.edgeCollections()) {
addToRemoveCollections(edgeCollection);
}
}
Result permRes =
checkDropGraphPermissions(graph, followersToBeRemoved, leadersToBeRemoved);
if (permRes.fail()) {
return OperationResult{std::move(permRes)};
}
VPackBuilder builder;
{
VPackObjectBuilder guard(&builder);
builder.add(StaticStrings::KeyString, VPackValue(graph.name()));
}
{ // Remove from _graphs
OperationOptions options;
options.waitForSync = waitForSync;
Result res;
OperationResult result;
SingleCollectionTransaction trx{ctx(), StaticStrings::GraphCollection,
AccessMode::Type::WRITE};
res = trx.begin();
if (res.fail()) {
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND);
}
VPackSlice search = builder.slice();
result = trx.remove(StaticStrings::GraphCollection, search, options);
res = trx.finish(result.result);
if (result.fail()) {
return result;
}
if (res.fail()) {
return OperationResult{res};
}
TRI_ASSERT(res.ok() && result.ok());
}
{ // drop collections
Result firstDropError;
// remove graph related collections.
// we are not able to do this in a transaction, so doing it afterwards.
// there may be no collections to drop when dropCollections is false.
TRI_ASSERT(dropCollections ||
(leadersToBeRemoved.empty() && followersToBeRemoved.empty()));
// drop followers (with distributeShardsLike) first and leaders (which occur
// in some distributeShardsLike) second.
for (auto const& collection : boost::join(followersToBeRemoved, leadersToBeRemoved)) {
Result dropResult;
Result found = methods::Collections::lookup(
ctx()->vocbase(), // vocbase to search
collection, // collection to find
[&](std::shared_ptr<LogicalCollection> const& coll) -> void {
TRI_ASSERT(coll);
dropResult = // result
arangodb::methods::Collections::drop(*coll, false, -1.0);
});
if (dropResult.fail()) {
LOG_TOPIC("04c88", WARN, Logger::GRAPHS)
<< "While removing graph `" << graph.name() << "`: "
<< "Dropping collection `" << collection << "` failed with error "
<< dropResult.errorNumber() << ": " << dropResult.errorMessage();
// save the first error:
if (firstDropError.ok()) {
firstDropError = dropResult;
}
}
}
if (firstDropError.fail()) {
return OperationResult{firstDropError};
}
}
return OperationResult{TRI_ERROR_NO_ERROR};
}
OperationResult GraphManager::pushCollectionIfMayBeDropped(
const std::string& colName, const std::string& graphName,
std::unordered_set<std::string>& toBeRemoved) {
VPackBuilder graphsBuilder;
OperationResult result = readGraphs(graphsBuilder, aql::PART_DEPENDENT);
if (result.fail()) {
return result;
}
VPackSlice graphs = graphsBuilder.slice();
bool collectionUnused = true;
TRI_ASSERT(graphs.get("graphs").isArray());
if (!graphs.get("graphs").isArray()) {
return OperationResult(TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT);
}
for (auto graph : VPackArrayIterator(graphs.get("graphs"))) {
graph = graph.resolveExternals();
if (!collectionUnused) {
// Short circuit
break;
}
if (graph.get(StaticStrings::KeyString).copyString() == graphName) {
continue;
}
// check edge definitions
VPackSlice edgeDefinitions = graph.get(StaticStrings::GraphEdgeDefinitions);
if (edgeDefinitions.isArray()) {
for (auto const& edgeDefinition : VPackArrayIterator(edgeDefinitions)) {
// edge collection
std::string collection = edgeDefinition.get("collection").copyString();
if (collection == colName) {
collectionUnused = false;
}
// from's
if (::ArrayContainsCollection(edgeDefinition.get(StaticStrings::GraphFrom), colName)) {
collectionUnused = false;
break;
}
if (::ArrayContainsCollection(edgeDefinition.get(StaticStrings::GraphTo), colName)) {
collectionUnused = false;
break;
}
}
} else {
return OperationResult(TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT);
}
// check orphan collections
VPackSlice orphanCollections = graph.get(StaticStrings::GraphOrphans);
if (orphanCollections.isArray()) {
if (::ArrayContainsCollection(orphanCollections, colName)) {
collectionUnused = false;
break;
}
}
}
if (collectionUnused) {
toBeRemoved.emplace(colName);
}
return OperationResult(TRI_ERROR_NO_ERROR);
}
Result GraphManager::checkDropGraphPermissions(
const Graph& graph, const std::unordered_set<std::string>& followersToBeRemoved,
const std::unordered_set<std::string>& leadersToBeRemoved) {
std::string const& databaseName = ctx()->vocbase().name();
std::stringstream stringstream;
stringstream << "When dropping graph " << databaseName << "." << graph.name() << ": ";
std::string const logprefix = stringstream.str();
ExecContext const* execContext = ExecContext::CURRENT;
if (execContext == nullptr) {
LOG_TOPIC("56c2f", DEBUG, Logger::GRAPHS) << logprefix << "Permissions are turned off.";
return TRI_ERROR_NO_ERROR;
}
bool mustDropAtLeastOneCollection =
!followersToBeRemoved.empty() || !leadersToBeRemoved.empty();
bool canUseDatabaseRW = execContext->canUseDatabase(auth::Level::RW);
if (mustDropAtLeastOneCollection && !canUseDatabaseRW) {
LOG_TOPIC("fdc57", DEBUG, Logger::GRAPHS)
<< logprefix << "Must drop at least one collection in " << databaseName
<< ", but don't have permissions.";
return TRI_ERROR_FORBIDDEN;
}
for (auto const& col : boost::join(followersToBeRemoved, leadersToBeRemoved)) {
// We need RW to drop a collection.
if (!execContext->canUseCollection(col, auth::Level::RW)) {
LOG_TOPIC("96384", DEBUG, Logger::GRAPHS)
<< logprefix << "No write access to " << databaseName << "." << col;
return TRI_ERROR_FORBIDDEN;
}
}
// We need RW on _graphs (which is the same as RW on the database). But in
// case we don't even have RO access, throw FORBIDDEN instead of READ_ONLY.
if (!execContext->canUseCollection(StaticStrings::GraphCollection, auth::Level::RO)) {
LOG_TOPIC("bfe63", DEBUG, Logger::GRAPHS)
<< logprefix << "No read access to " << databaseName << "."
<< StaticStrings::GraphCollection;
return TRI_ERROR_FORBIDDEN;
}
// Note that this check includes the following check from before
// if (mustDropAtLeastOneCollection && !canUseDatabaseRW)
// as canUseDatabase(RW) <=> canUseCollection("_...", RW).
// However, in case a collection has to be created but can't, we have to throw
// FORBIDDEN instead of READ_ONLY for backwards compatibility.
if (!execContext->canUseCollection(StaticStrings::GraphCollection, auth::Level::RW)) {
LOG_TOPIC("bbb09", DEBUG, Logger::GRAPHS)
<< logprefix << "No write access to " << databaseName << "."
<< StaticStrings::GraphCollection;
return TRI_ERROR_ARANGO_READ_ONLY;
}
return TRI_ERROR_NO_ERROR;
}
ResultT<std::unique_ptr<Graph>> GraphManager::buildGraphFromInput(std::string const& graphName,
VPackSlice input) const {
try {
TRI_ASSERT(input.isObject());
if (ServerState::instance()->isCoordinator()) {
// validate numberOfShards and replicationFactor
Result res = ShardingInfo::validateShardsAndReplicationFactor(input.get("options"));
if (res.fail()) {
return res;
}
}
return Graph::fromUserInput(graphName, input, input.get(StaticStrings::GraphOptions));
} catch (arangodb::basics::Exception const& e) {
return Result{e.code(), e.message()};
} catch (...) {
return {TRI_ERROR_INTERNAL};
}
}