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

1002 lines
35 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 Tobias Gödderz & Heiko Kernbach
////////////////////////////////////////////////////////////////////////////////
#include "GraphOperations.h"
#include <arangod/Transaction/V8Context.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/Query.h"
#include "Basics/StaticStrings.h"
#include "Basics/VelocyPackHelper.h"
#include "Graph/Graph.h"
#include "Graph/GraphManager.h"
#include "RestServer/QueryRegistryFeature.h"
#include "Transaction/Methods.h"
#include "Transaction/SmartContext.h"
#include "Transaction/StandaloneContext.h"
#include "Utils/CollectionNameResolver.h"
#include "Utils/ExecContext.h"
#include "Utils/OperationOptions.h"
#include "Utils/SingleCollectionTransaction.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/Methods/Collections.h"
using namespace arangodb;
using namespace arangodb::graph;
std::shared_ptr<transaction::Context> GraphOperations::ctx() const {
return transaction::StandaloneContext::Create(_vocbase);
};
void GraphOperations::checkForUsedEdgeCollections(const Graph& graph,
const std::string& collectionName,
std::unordered_set<std::string>& possibleEdgeCollections) {
for (auto const& it : graph.edgeDefinitions()) {
if (it.second.isVertexCollectionUsed(collectionName)) {
possibleEdgeCollections.emplace(it.second.getName());
}
}
}
OperationResult GraphOperations::changeEdgeDefinitionForGraph(Graph& graph,
EdgeDefinition const& newEdgeDef,
bool waitForSync,
transaction::Methods& trx) {
VPackBuilder builder;
// remove old definition, insert the new one instead
Result res = graph.replaceEdgeDefinition(newEdgeDef);
if (res.fail()) {
return OperationResult(res);
}
builder.openObject();
graph.toPersistence(builder);
builder.close();
GraphManager gmngr{_vocbase};
std::set<std::string> newCollections;
// add collections that didn't exist in the graph before to newCollections:
for (auto const& it : boost::join(newEdgeDef.getFrom(), newEdgeDef.getTo())) {
if (!graph.hasVertexCollection(it) && !graph.hasOrphanCollection(it)) {
newCollections.emplace(it);
}
}
VPackBuilder collectionOptions;
collectionOptions.openObject();
_graph.createCollectionOptions(collectionOptions, waitForSync);
collectionOptions.close();
for (auto const& newCollection : newCollections) {
// While the collection is new in the graph, it may still already exist.
if (GraphManager::getCollectionByName(_vocbase, newCollection)) {
continue;
}
OperationResult result = gmngr.createVertexCollection(newCollection, waitForSync,
collectionOptions.slice());
if (result.fail()) {
return result;
}
}
OperationOptions options;
options.waitForSync = waitForSync;
// now write to database
return trx.update(StaticStrings::GraphCollection, builder.slice(), options);
}
OperationResult GraphOperations::eraseEdgeDefinition(bool waitForSync, std::string edgeDefinitionName,
bool dropCollection) {
// check if edgeCollection is available
OperationResult result = checkEdgeCollectionAvailability(edgeDefinitionName);
if (result.fail()) {
return result;
}
if (dropCollection && !hasRWPermissionsFor(edgeDefinitionName)) {
return OperationResult{TRI_ERROR_FORBIDDEN};
}
// remove edgeDefinition from graph config
_graph.removeEdgeDefinition(edgeDefinitionName);
OperationOptions options;
options.waitForSync = waitForSync;
VPackBuilder builder;
builder.openObject();
_graph.toPersistence(builder);
builder.close();
SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection,
AccessMode::Type::WRITE);
trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION);
Result res = trx.begin();
if (!res.ok()) {
res = trx.finish(res);
return OperationResult(res);
}
result = trx.update(StaticStrings::GraphCollection, builder.slice(), options);
if (dropCollection) {
std::unordered_set<std::string> collectionsToBeRemoved;
GraphManager gmngr{_vocbase};
// add the edge collection itself for removal
gmngr.pushCollectionIfMayBeDropped(edgeDefinitionName, _graph.name(), collectionsToBeRemoved);
for (auto const& collection : collectionsToBeRemoved) {
Result resIn;
Result found = methods::Collections::lookup(
_vocbase, // vocbase to search
collection, // collection to find
[&](std::shared_ptr<LogicalCollection> const& coll) -> void { // callback if found
TRI_ASSERT(coll);
resIn = methods::Collections::drop(*coll, false, -1.0);
});
if (found.fail()) {
res = trx.finish(result.result);
return OperationResult(res);
} else if (resIn.fail()) {
res = trx.finish(result.result);
return OperationResult(res);
}
}
}
res = trx.finish(result.result);
if (result.ok() && res.fail()) {
return OperationResult(res);
}
return result;
}
OperationResult GraphOperations::checkEdgeCollectionAvailability(std::string edgeCollectionName) {
bool found = _graph.edgeCollections().find(edgeCollectionName) !=
_graph.edgeCollections().end();
if (!found) {
return OperationResult(TRI_ERROR_GRAPH_EDGE_COLLECTION_NOT_USED);
}
return OperationResult(TRI_ERROR_NO_ERROR);
}
OperationResult GraphOperations::checkVertexCollectionAvailability(std::string vertexCollectionName) {
std::shared_ptr<LogicalCollection> def =
GraphManager::getCollectionByName(_vocbase, vertexCollectionName);
if (def == nullptr) {
return OperationResult(
Result(TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST,
vertexCollectionName + " " +
std::string{TRI_errno_string(TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST)}));
}
return OperationResult(TRI_ERROR_NO_ERROR);
}
OperationResult GraphOperations::editEdgeDefinition(VPackSlice edgeDefinitionSlice,
bool waitForSync,
const std::string& edgeDefinitionName) {
auto maybeEdgeDef = EdgeDefinition::createFromVelocypack(edgeDefinitionSlice);
if (!maybeEdgeDef) {
return OperationResult{std::move(maybeEdgeDef).result()};
}
EdgeDefinition const& edgeDefinition = maybeEdgeDef.get();
// check if edgeCollection is available
OperationResult result = checkEdgeCollectionAvailability(edgeDefinitionName);
if (result.fail()) {
return result;
}
Result permRes = checkEdgeDefinitionPermissions(edgeDefinition);
if (permRes.fail()) {
return OperationResult{permRes};
}
GraphManager gmngr{_vocbase};
VPackBuilder collectionsOptions;
collectionsOptions.openObject();
_graph.createCollectionOptions(collectionsOptions, waitForSync);
collectionsOptions.close();
result = gmngr.findOrCreateCollectionsByEdgeDefinition(edgeDefinition, waitForSync,
collectionsOptions.slice());
if (result.fail()) {
return result;
}
if (!_graph.hasEdgeCollection(edgeDefinition.getName())) {
return OperationResult(TRI_ERROR_GRAPH_EDGE_COLLECTION_NOT_USED);
}
// change definition for ALL graphs
VPackBuilder graphsBuilder;
gmngr.readGraphs(graphsBuilder, arangodb::aql::PART_DEPENDENT);
VPackSlice graphs = graphsBuilder.slice();
if (!graphs.get("graphs").isArray()) {
return OperationResult{TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT};
}
SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection,
AccessMode::Type::WRITE);
Result res = trx.begin();
if (!res.ok()) {
return OperationResult(res);
}
for (auto singleGraph : VPackArrayIterator(graphs.get("graphs"))) {
std::unique_ptr<Graph> graph =
Graph::fromPersistence(singleGraph.resolveExternals(), _vocbase);
result = changeEdgeDefinitionForGraph(*(graph.get()), edgeDefinition, waitForSync, trx);
}
if (result.fail()) {
return result;
}
res = trx.finish(TRI_ERROR_NO_ERROR);
if (result.ok() && res.fail()) {
return OperationResult(res);
}
return result;
};
OperationResult GraphOperations::addOrphanCollection(VPackSlice document, bool waitForSync,
bool createCollection) {
GraphManager gmngr{_vocbase};
std::string collectionName = document.get("collection").copyString();
std::shared_ptr<LogicalCollection> def;
OperationResult result;
VPackBuilder collectionsOptions;
collectionsOptions.openObject();
_graph.createCollectionOptions(collectionsOptions, waitForSync);
collectionsOptions.close();
if (_graph.hasVertexCollection(collectionName)) {
if (_graph.hasOrphanCollection(collectionName)) {
return OperationResult(TRI_ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS);
}
return OperationResult(
Result(TRI_ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF,
collectionName + " " +
std::string{TRI_errno_string(TRI_ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF)}));
}
def = GraphManager::getCollectionByName(_vocbase, collectionName);
if (def == nullptr) {
if (createCollection) {
result = gmngr.createVertexCollection(collectionName, waitForSync,
collectionsOptions.slice());
if (result.fail()) {
return result;
}
} else {
return OperationResult(
Result(TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST,
collectionName + " " +
std::string{TRI_errno_string(TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST)}));
}
} else {
if (def->type() != TRI_COL_TYPE_DOCUMENT) {
return OperationResult(TRI_ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX);
}
auto res = _graph.validateCollection(*(def.get()));
if (res.fail()) {
return OperationResult{std::move(res)};
}
}
// add orphan collection to graph
_graph.addOrphanCollection(std::move(collectionName));
VPackBuilder builder;
builder.openObject();
_graph.toPersistence(builder);
builder.close();
SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection,
AccessMode::Type::WRITE);
Result res = trx.begin();
if (!res.ok()) {
trx.finish(TRI_ERROR_NO_ERROR);
return OperationResult(res);
}
OperationOptions options;
options.waitForSync = waitForSync;
result = trx.update(StaticStrings::GraphCollection, builder.slice(), options);
res = trx.finish(result.result);
if (result.ok() && res.fail()) {
return OperationResult(res);
}
return result;
}
OperationResult GraphOperations::eraseOrphanCollection(bool waitForSync, std::string collectionName,
bool dropCollection) {
// check if collection exists within the orphan collections
bool found = false;
for (auto const& oName : _graph.orphanCollections()) {
if (oName == collectionName) {
found = true;
break;
}
}
if (!found) {
return OperationResult(TRI_ERROR_GRAPH_NOT_IN_ORPHAN_COLLECTION);
}
// check if collection exists in the database
OperationResult result = checkVertexCollectionAvailability(collectionName);
if (result.fail()) {
return result;
}
if (!hasRWPermissionsFor(collectionName)) {
return OperationResult{TRI_ERROR_FORBIDDEN};
}
Result res = _graph.removeOrphanCollection(std::move(collectionName));
if (res.fail()) {
return OperationResult(res);
}
VPackBuilder builder;
builder.openObject();
_graph.toPersistence(builder);
builder.close();
SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection,
AccessMode::Type::WRITE);
trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION);
res = trx.begin();
if (!res.ok()) {
return OperationResult(res);
}
OperationOptions options;
options.waitForSync = waitForSync;
result = trx.update(StaticStrings::GraphCollection, builder.slice(), options);
res = trx.finish(result.result);
if (dropCollection) {
std::unordered_set<std::string> collectionsToBeRemoved;
GraphManager gmngr{_vocbase};
gmngr.pushCollectionIfMayBeDropped(collectionName, "", collectionsToBeRemoved);
for (auto const& collection : collectionsToBeRemoved) {
Result resIn;
Result found = methods::Collections::lookup(
_vocbase, // vocbase to search
collection, // collection to find
[&](std::shared_ptr<LogicalCollection> const& coll) -> void { // callback if found
TRI_ASSERT(coll);
resIn = methods::Collections::drop(*coll, false, -1.0);
});
if (found.fail()) {
return OperationResult(res);
} else if (resIn.fail()) {
return OperationResult(res);
}
}
}
if (result.ok() && res.fail()) {
return OperationResult(res);
}
return result;
}
OperationResult GraphOperations::addEdgeDefinition(VPackSlice edgeDefinitionSlice,
bool waitForSync) {
ResultT<EdgeDefinition const*> defRes = _graph.addEdgeDefinition(edgeDefinitionSlice);
if (defRes.fail()) {
return OperationResult(std::move(defRes).result());
}
// Guaranteed to be non nullptr
TRI_ASSERT(defRes.get() != nullptr);
// ... in different graph
GraphManager gmngr{_vocbase};
OperationResult result{
gmngr.checkForEdgeDefinitionConflicts(*(defRes.get()), _graph.name())};
if (result.fail()) {
// If this fails we will not persist.
return result;
}
Result res = gmngr.ensureCollections(&_graph, waitForSync);
if (res.fail()) {
return OperationResult{std::move(res)};
}
// finally save the graph
return gmngr.storeGraph(_graph, waitForSync, true);
};
// vertices
// TODO check if collection is a vertex collection in _graph?
// TODO are orphans allowed?
OperationResult GraphOperations::getVertex(std::string const& collectionName,
std::string const& key,
boost::optional<TRI_voc_rid_t> rev) {
return getDocument(collectionName, key, std::move(rev));
};
// TODO check if definitionName is an edge collection in _graph?
OperationResult GraphOperations::getEdge(const std::string& definitionName,
const std::string& key,
boost::optional<TRI_voc_rid_t> rev) {
return getDocument(definitionName, key, std::move(rev));
}
OperationResult GraphOperations::getDocument(std::string const& collectionName,
std::string const& key,
boost::optional<TRI_voc_rid_t> rev) {
OperationOptions options;
options.ignoreRevs = !rev.is_initialized();
VPackBufferPtr searchBuffer = _getSearchSlice(key, rev);
VPackSlice search{searchBuffer->data()};
// find and load collection given by name or identifier
SingleCollectionTransaction trx(ctx(), collectionName, AccessMode::Type::READ);
trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION);
Result res = trx.begin();
if (!res.ok()) {
return OperationResult(res);
}
OperationResult result = trx.document(collectionName, search, options);
res = trx.finish(result.result);
if (result.ok() && res.fail()) {
return OperationResult(res);
}
return result;
}
GraphOperations::VPackBufferPtr GraphOperations::_getSearchSlice(
const std::string& key, boost::optional<TRI_voc_rid_t>& rev) const {
VPackBuilder builder;
{
VPackObjectBuilder guard(&builder);
builder.add(StaticStrings::KeyString, VPackValue(key));
if (rev) {
builder.add(StaticStrings::RevString, VPackValue(TRI_RidToString(rev.get())));
}
}
return builder.buffer();
}
OperationResult GraphOperations::removeEdge(const std::string& definitionName,
const std::string& key,
boost::optional<TRI_voc_rid_t> rev,
bool waitForSync, bool returnOld) {
return removeEdgeOrVertex(definitionName, key, rev, waitForSync, returnOld);
}
OperationResult GraphOperations::modifyDocument(
std::string const& collectionName, std::string const& key, VPackSlice document,
bool isPatch, boost::optional<TRI_voc_rid_t> rev, bool waitForSync,
bool returnOld, bool returnNew, bool keepNull, transaction::Methods& trx) {
// extract the revision, if single document variant and header given:
std::unique_ptr<VPackBuilder> builder;
VPackSlice keyInBody = document.get(StaticStrings::KeyString);
if ((rev && TRI_ExtractRevisionId(document) != rev.get()) || keyInBody.isNone() ||
keyInBody.isNull() || (keyInBody.isString() && keyInBody.copyString() != key)) {
// We need to rewrite the document with the given revision and key:
builder = std::make_unique<VPackBuilder>();
{
VPackObjectBuilder guard(builder.get());
TRI_SanitizeObject(document, *builder);
builder->add(StaticStrings::KeyString, VPackValue(key));
if (rev) {
builder->add(StaticStrings::RevString, VPackValue(TRI_RidToString(rev.get())));
}
}
document = builder->slice();
}
OperationOptions options;
options.ignoreRevs = !rev.is_initialized();
options.waitForSync = waitForSync;
options.returnNew = returnNew;
options.returnOld = returnOld;
OperationResult result;
if (isPatch) {
options.keepNull = keepNull;
result = trx.update(collectionName, document, options);
} else {
result = trx.replace(collectionName, document, options);
}
Result res = trx.finish(result.result);
if (result.ok() && res.fail()) {
return OperationResult(res);
}
return result;
}
OperationResult GraphOperations::createDocument(transaction::Methods* trx,
std::string const& collectionName,
VPackSlice document,
bool waitForSync, bool returnNew) {
OperationOptions options;
options.waitForSync = waitForSync;
options.returnNew = returnNew;
OperationResult result = trx->insert(collectionName, document, options);
result.result = trx->finish(result.result);
return result;
}
OperationResult GraphOperations::updateEdge(const std::string& definitionName,
const std::string& key, VPackSlice document,
boost::optional<TRI_voc_rid_t> rev,
bool waitForSync, bool returnOld,
bool returnNew, bool keepNull) {
OperationResult res;
std::unique_ptr<transaction::Methods> trx;
std::tie(res, trx) = validateEdge(definitionName, document, waitForSync, true);
if (res.fail()) {
return res;
}
TRI_ASSERT(trx != nullptr);
return modifyDocument(definitionName, key, document, true, std::move(rev),
waitForSync, returnOld, returnNew, keepNull, *trx.get());
}
OperationResult GraphOperations::replaceEdge(const std::string& definitionName,
const std::string& key, VPackSlice document,
boost::optional<TRI_voc_rid_t> rev,
bool waitForSync, bool returnOld,
bool returnNew, bool keepNull) {
OperationResult res;
std::unique_ptr<transaction::Methods> trx;
std::tie(res, trx) = validateEdge(definitionName, document, waitForSync, false);
if (res.fail()) {
return res;
}
TRI_ASSERT(trx != nullptr);
return modifyDocument(definitionName, key, document, false, std::move(rev),
waitForSync, returnOld, returnNew, keepNull, *trx.get());
}
std::pair<OperationResult, std::unique_ptr<transaction::Methods>> GraphOperations::validateEdge(
const std::string& definitionName, const VPackSlice& document,
bool waitForSync, bool isUpdate) {
std::string fromCollectionName;
std::string fromCollectionKey;
std::string toCollectionName;
std::string toCollectionKey;
OperationResult res;
bool foundEdgeDefinition;
std::tie(res, foundEdgeDefinition) =
validateEdgeContent(document, fromCollectionName, fromCollectionKey,
toCollectionName, toCollectionKey, isUpdate);
if (res.fail()) {
return std::make_pair(std::move(res), nullptr);
}
std::vector<std::string> readCollections;
std::vector<std::string> writeCollections;
std::vector<std::string> exclusiveCollections;
if (foundEdgeDefinition) {
readCollections.emplace_back(fromCollectionName);
readCollections.emplace_back(toCollectionName);
}
writeCollections.emplace_back(definitionName);
transaction::Options trxOptions;
trxOptions.waitForSync = waitForSync;
auto trx = std::make_unique<transaction::Methods>(ctx(), readCollections, writeCollections,
exclusiveCollections, trxOptions);
Result tRes = trx->begin();
if (!tRes.ok()) {
return std::make_pair(OperationResult(tRes), nullptr);
}
if (foundEdgeDefinition) {
res = validateEdgeVertices(fromCollectionName, fromCollectionKey,
toCollectionName, toCollectionKey, *trx.get());
if (res.fail()) {
return std::make_pair(std::move(res), nullptr);
}
}
return std::make_pair(std::move(res), std::move(trx));
}
OperationResult GraphOperations::validateEdgeVertices(
const std::string& fromCollectionName, const std::string& fromCollectionKey,
const std::string& toCollectionName, const std::string& toCollectionKey,
transaction::Methods& trx) {
VPackBuilder bT;
{
VPackObjectBuilder guard(&bT);
bT.add(StaticStrings::KeyString, VPackValue(toCollectionKey));
}
VPackBuilder bF;
{
VPackObjectBuilder guard(&bF);
bF.add(StaticStrings::KeyString, VPackValue(fromCollectionKey));
}
OperationOptions options;
OperationResult resultFrom = trx.document(fromCollectionName, bF.slice(), options);
OperationResult resultTo = trx.document(toCollectionName, bT.slice(), options);
// actual result doesn't matter here
if (!resultFrom.ok()) {
trx.finish(resultFrom.result);
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND);
} else if (!resultTo.ok()) {
trx.finish(resultTo.result);
return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND);
}
return OperationResult(TRI_ERROR_NO_ERROR);
}
std::pair<OperationResult, bool> GraphOperations::validateEdgeContent(
const VPackSlice& document, std::string& fromCollectionName, std::string& fromCollectionKey,
std::string& toCollectionName, std::string& toCollectionKey, bool isUpdate) {
VPackSlice fromStringSlice = document.get(StaticStrings::FromString);
VPackSlice toStringSlice = document.get(StaticStrings::ToString);
if (fromStringSlice.isNone() || toStringSlice.isNone()) {
if (isUpdate) {
return std::make_pair(OperationResult(TRI_ERROR_NO_ERROR), false);
}
return std::make_pair(OperationResult(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE), false);
}
std::string fromString = fromStringSlice.copyString();
std::string toString = toStringSlice.copyString();
size_t pos = fromString.find('/');
if (pos != std::string::npos) {
fromCollectionName = fromString.substr(0, pos);
fromCollectionKey = fromString.substr(pos + 1, fromString.length());
} else {
return std::make_pair(OperationResult(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE), true);
}
pos = toString.find('/');
if (pos != std::string::npos) {
toCollectionName = toString.substr(0, pos);
toCollectionKey = toString.substr(pos + 1, toString.length());
} else {
return std::make_pair(OperationResult(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE), true);
}
// check if vertex collections are part of the graph definition
auto it = _graph.vertexCollections().find(fromCollectionName);
if (it == _graph.vertexCollections().end()) {
// not found from vertex
return std::make_pair(OperationResult(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND), true);
}
it = _graph.vertexCollections().find(toCollectionName);
if (it == _graph.vertexCollections().end()) {
// not found to vertex
return std::make_pair(OperationResult(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND), true);
}
return std::make_pair(OperationResult(TRI_ERROR_NO_ERROR), true);
}
OperationResult GraphOperations::createEdge(const std::string& definitionName,
VPackSlice document,
bool waitForSync, bool returnNew) {
OperationResult res;
std::unique_ptr<transaction::Methods> trx;
std::tie(res, trx) = validateEdge(definitionName, document, waitForSync, false);
if (res.fail()) {
return res;
}
TRI_ASSERT(trx != nullptr);
return createDocument(&(*trx.get()), definitionName, document, waitForSync, returnNew);
}
OperationResult GraphOperations::updateVertex(const std::string& collectionName,
const std::string& key, VPackSlice document,
boost::optional<TRI_voc_rid_t> rev,
bool waitForSync, bool returnOld,
bool returnNew, bool keepNull) {
std::vector<std::string> writeCollections;
writeCollections.emplace_back(collectionName);
transaction::Options trxOptions;
trxOptions.waitForSync = waitForSync;
transaction::Methods trx(ctx(), {}, writeCollections, {}, trxOptions);
Result tRes = trx.begin();
if (!tRes.ok()) {
return OperationResult(tRes);
}
return modifyDocument(collectionName, key, document, true, std::move(rev),
waitForSync, returnOld, returnNew, keepNull, trx);
}
OperationResult GraphOperations::replaceVertex(const std::string& collectionName,
const std::string& key, VPackSlice document,
boost::optional<TRI_voc_rid_t> rev,
bool waitForSync, bool returnOld,
bool returnNew, bool keepNull) {
std::vector<std::string> writeCollections;
writeCollections.emplace_back(collectionName);
transaction::Options trxOptions;
trxOptions.waitForSync = waitForSync;
transaction::Methods trx(ctx(), {}, writeCollections, {}, trxOptions);
Result tRes = trx.begin();
if (!tRes.ok()) {
return OperationResult(tRes);
}
return modifyDocument(collectionName, key, document, false, std::move(rev),
waitForSync, returnOld, returnNew, keepNull, trx);
}
OperationResult GraphOperations::createVertex(const std::string& collectionName,
VPackSlice document,
bool waitForSync, bool returnNew) {
transaction::Options trxOptions;
std::vector<std::string> writeCollections;
writeCollections.emplace_back(collectionName);
transaction::Methods trx(ctx(), {}, writeCollections, {}, trxOptions);
Result res = trx.begin();
if (!res.ok()) {
return OperationResult(res);
}
return createDocument(&trx, collectionName, document, waitForSync, returnNew);
}
OperationResult GraphOperations::removeEdgeOrVertex(const std::string& collectionName,
const std::string& key,
boost::optional<TRI_voc_rid_t> rev,
bool waitForSync, bool returnOld) {
OperationOptions options;
options.waitForSync = waitForSync;
options.returnOld = returnOld;
options.ignoreRevs = !rev.is_initialized();
VPackBufferPtr searchBuffer = _getSearchSlice(key, rev);
VPackSlice search{searchBuffer->data()};
// check for used edge definitions in ALL graphs
GraphManager gmngr{_vocbase};
std::unordered_set<std::string> possibleEdgeCollections;
auto callback = [&](std::unique_ptr<Graph> graph) -> Result {
checkForUsedEdgeCollections(*graph, collectionName, possibleEdgeCollections);
return Result{};
};
Result res = gmngr.applyOnAllGraphs(callback);
if (res.fail()) {
return OperationResult(res);
}
auto edgeCollections = _graph.edgeCollections();
std::vector<std::string> trxCollections;
trxCollections.emplace_back(collectionName);
CollectionNameResolver resolver{_vocbase};
for (auto const& it : edgeCollections) {
trxCollections.emplace_back(it);
auto col = resolver.getCollection(it);
if (col && col->isSmart() && col->type() == TRI_COL_TYPE_EDGE) {
for (auto const& rn : col->realNames()) {
trxCollections.emplace_back(rn);
}
}
}
for (auto const& it : possibleEdgeCollections) {
trxCollections.emplace_back(it); // add to trx collections
edgeCollections.emplace(it); // but also to edgeCollections for later iteration
}
auto ctx = std::make_shared<transaction::StandaloneSmartContext>(_vocbase);
transaction::Options trxOptions;
trxOptions.waitForSync = waitForSync;
transaction::Methods trx{ctx, {}, trxCollections, {}, trxOptions};
res = trx.begin();
if (!res.ok()) {
return OperationResult(res);
}
OperationResult result = trx.remove(collectionName, search, options);
{
aql::QueryString const queryString{
"FOR e IN @@collection "
"FILTER e._from == @toDeleteId "
"OR e._to == @toDeleteId "
"REMOVE e IN @@collection"};
std::string const toDeleteId = collectionName + "/" + key;
for (auto const& edgeCollection : edgeCollections) {
auto bindVars = std::make_shared<VPackBuilder>();
bindVars->add(VPackValue(VPackValueType::Object));
bindVars->add("@collection", VPackValue(edgeCollection));
bindVars->add("toDeleteId", VPackValue(toDeleteId));
bindVars->close();
arangodb::aql::Query query(false, _vocbase, queryString, bindVars,
nullptr, arangodb::aql::PART_DEPENDENT);
query.setTransactionContext(ctx); // hack to share the same transaction
auto queryResult = query.executeSync(QueryRegistryFeature::registry());
if (queryResult.result.fail()) {
return OperationResult(std::move(queryResult.result));
}
}
}
res = trx.finish(result.result);
if (result.ok() && res.fail()) {
return OperationResult(res);
}
return result;
}
OperationResult GraphOperations::removeVertex(const std::string& collectionName,
const std::string& key,
boost::optional<TRI_voc_rid_t> rev,
bool waitForSync, bool returnOld) {
return removeEdgeOrVertex(collectionName, key, rev, waitForSync, returnOld);
}
bool GraphOperations::collectionExists(std::string const& collection) const {
GraphManager gmngr{_vocbase};
return gmngr.collectionExists(collection);
}
bool GraphOperations::hasROPermissionsFor(std::string const& collection) const {
return hasPermissionsFor(collection, auth::Level::RO);
}
bool GraphOperations::hasRWPermissionsFor(std::string const& collection) const {
return hasPermissionsFor(collection, auth::Level::RW);
}
bool GraphOperations::hasPermissionsFor(std::string const& collection, auth::Level level) const {
std::string const& databaseName = _vocbase.name();
std::stringstream stringstream;
stringstream << "When checking " << convertFromAuthLevel(level)
<< " permissions for " << databaseName << "." << collection << ": ";
std::string const logprefix = stringstream.str();
ExecContext const* execContext = ExecContext::CURRENT;
if (execContext == nullptr) {
LOG_TOPIC("08e1f", DEBUG, Logger::GRAPHS) << logprefix << "Permissions are turned off.";
return true;
}
if (execContext->canUseCollection(collection, level)) {
return true;
}
LOG_TOPIC("ef8d1", DEBUG, Logger::GRAPHS) << logprefix << "Not allowed.";
return false;
}
Result GraphOperations::checkEdgeDefinitionPermissions(EdgeDefinition const& edgeDefinition) const {
std::string const& databaseName = _vocbase.name();
std::stringstream stringstream;
stringstream << "When checking permissions for edge definition `"
<< edgeDefinition.getName() << "` of graph `" << databaseName
<< "." << graph().name() << "`: ";
std::string const logprefix = stringstream.str();
ExecContext const* execContext = ExecContext::CURRENT;
if (execContext == nullptr) {
LOG_TOPIC("18e8e", DEBUG, Logger::GRAPHS) << logprefix << "Permissions are turned off.";
return TRI_ERROR_NO_ERROR;
}
// collect all used collections in one container
std::set<std::string> graphCollections;
setUnion(graphCollections, edgeDefinition.getFrom());
setUnion(graphCollections, edgeDefinition.getTo());
graphCollections.emplace(edgeDefinition.getName());
bool canUseDatabaseRW = execContext->canUseDatabase(auth::Level::RW);
for (auto const& col : graphCollections) {
// 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("e8a53", DEBUG, Logger::GRAPHS)
<< logprefix << "No read access to " << databaseName << "." << col;
return TRI_ERROR_FORBIDDEN;
}
if (!collectionExists(col) && !canUseDatabaseRW) {
LOG_TOPIC("2bcf2", DEBUG, Logger::GRAPHS)
<< logprefix << "Creation of " << databaseName << "." << col
<< " is not allowed.";
return TRI_ERROR_FORBIDDEN;
}
}
return TRI_ERROR_NO_ERROR;
}