mirror of https://gitee.com/bigwinds/arangodb
1010 lines
36 KiB
C++
1010 lines
36 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
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "RestGraphHandler.h"
|
|
|
|
#include <velocypack/Collection.h>
|
|
#include <boost/optional.hpp>
|
|
#include <utility>
|
|
|
|
#include "Aql/Query.h"
|
|
#include "Basics/StringUtils.h"
|
|
#include "Basics/VelocyPackHelper.h"
|
|
#include "Graph/Graph.h"
|
|
#include "Graph/GraphManager.h"
|
|
#include "Graph/GraphOperations.h"
|
|
#include "RestServer/QueryRegistryFeature.h"
|
|
#include "Transaction/StandaloneContext.h"
|
|
#include "Utils/OperationOptions.h"
|
|
#include "Utils/SingleCollectionTransaction.h"
|
|
|
|
using namespace arangodb;
|
|
using namespace arangodb::graph;
|
|
using VelocyPackHelper = arangodb::basics::VelocyPackHelper;
|
|
|
|
RestGraphHandler::RestGraphHandler(GeneralRequest* request, GeneralResponse* response)
|
|
: RestVocbaseBaseHandler(request, response), _gmngr(_vocbase) {}
|
|
|
|
RestStatus RestGraphHandler::execute() {
|
|
Result res = executeGharial();
|
|
if (res.fail()) {
|
|
generateError(res);
|
|
return RestStatus::FAIL;
|
|
}
|
|
// The url is required to properly generate the result!
|
|
return RestStatus::DONE;
|
|
}
|
|
|
|
Result RestGraphHandler::executeGharial() {
|
|
auto suffix = request()->suffixes().begin();
|
|
auto end = request()->suffixes().end();
|
|
|
|
auto getNextSuffix = [&suffix]() {
|
|
return basics::StringUtils::urlDecodePath(*suffix++);
|
|
};
|
|
|
|
auto noMoreSuffixes = [&suffix, &end]() { return suffix == end; };
|
|
|
|
if (noMoreSuffixes()) {
|
|
// /_api/gharial
|
|
return graphsAction();
|
|
}
|
|
|
|
std::string const& graphName = getNextSuffix();
|
|
|
|
std::unique_ptr<Graph> graph = getGraph(graphName);
|
|
|
|
// Guaranteed
|
|
TRI_ASSERT(graph != nullptr);
|
|
|
|
if (noMoreSuffixes()) {
|
|
// /_api/gharial/{graph-name}
|
|
return graphAction(*(graph.get()));
|
|
}
|
|
|
|
std::string const& collType = getNextSuffix();
|
|
|
|
const char* vertex = "vertex";
|
|
const char* edge = "edge";
|
|
if (collType != vertex && collType != edge) {
|
|
return {TRI_ERROR_HTTP_NOT_FOUND};
|
|
}
|
|
|
|
if (noMoreSuffixes()) {
|
|
if (collType == vertex) {
|
|
// /_api/gharial/{graph-name}/vertex
|
|
return vertexSetsAction(*(graph.get()));
|
|
} else if (collType == edge) {
|
|
// /_api/gharial/{graph-name}/edge
|
|
return edgeSetsAction(*(graph.get()));
|
|
}
|
|
}
|
|
|
|
std::string const& setName = getNextSuffix();
|
|
|
|
// TODO Add tests for this, especially with existing collections & vertices
|
|
// where the collection is only missing in the graph.
|
|
// TODO The existing tests seem to be inconsistent about this:
|
|
// e.g., deleting a non-existent vertex collection is expected to throw
|
|
// TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST but reading a vertex of a
|
|
// non-existent collection is expected to throw
|
|
// ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.
|
|
// This is commented out until the tests are changed.
|
|
// TODO The existing API seems to ignore the type of the collection for
|
|
// most operations. So fetching an edge via
|
|
// /_api/gharial/{graph}/vertex/{coll}/{key} works just fine. Should this be
|
|
// changed? One way or the other, make sure there are tests for the desired
|
|
// behavior!
|
|
/*
|
|
if (collType == vertex) {
|
|
if (graph->vertexCollections().find(setName) ==
|
|
graph->vertexCollections().end()) {
|
|
generateError(TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST);
|
|
return RestStatus::DONE;
|
|
}
|
|
} else if (collType == edge) {
|
|
if (graph->edgeCollections().find(setName) ==
|
|
graph->edgeCollections().end()) {
|
|
generateError(TRI_ERROR_GRAPH_EDGE_COL_DOES_NOT_EXIST);
|
|
return RestStatus::DONE;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (noMoreSuffixes()) {
|
|
if (collType == vertex) {
|
|
// /_api/gharial/{graph-name}/vertex/{collection-name}
|
|
return vertexSetAction(*(graph.get()), setName);
|
|
} else if (collType == edge) {
|
|
// /_api/gharial/{graph-name}/edge/{definition-name}
|
|
return edgeSetAction(*(graph.get()), setName);
|
|
}
|
|
}
|
|
|
|
std::string const& elementKey = getNextSuffix();
|
|
|
|
if (noMoreSuffixes()) {
|
|
if (collType == vertex) {
|
|
// /_api/gharial/{graph-name}/vertex/{collection-name}/{vertex-key}
|
|
return vertexAction(*(graph.get()), setName, elementKey);
|
|
} else if (collType == edge) {
|
|
// /_api/gharial/{graph-name}/edge/{definition-name}/{edge-key}
|
|
return edgeAction(*(graph.get()), setName, elementKey);
|
|
}
|
|
}
|
|
|
|
return {TRI_ERROR_HTTP_NOT_FOUND};
|
|
}
|
|
|
|
Result RestGraphHandler::graphAction(Graph& graph) {
|
|
switch (request()->requestType()) {
|
|
case RequestType::GET:
|
|
return graphActionReadGraphConfig(graph);
|
|
case RequestType::DELETE_REQ:
|
|
return graphActionRemoveGraph(graph);
|
|
default:;
|
|
}
|
|
return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED};
|
|
}
|
|
|
|
Result RestGraphHandler::graphsAction() {
|
|
switch (request()->requestType()) {
|
|
case RequestType::GET:
|
|
return graphActionReadGraphs();
|
|
case RequestType::POST:
|
|
return graphActionCreateGraph();
|
|
default:;
|
|
}
|
|
return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED};
|
|
}
|
|
|
|
Result RestGraphHandler::vertexSetsAction(Graph& graph) {
|
|
switch (request()->requestType()) {
|
|
case RequestType::GET:
|
|
return graphActionReadConfig(graph, TRI_COL_TYPE_DOCUMENT, GraphProperty::VERTICES);
|
|
case RequestType::POST:
|
|
return modifyVertexDefinition(graph, VertexDefinitionAction::CREATE, "");
|
|
default:;
|
|
}
|
|
return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED};
|
|
}
|
|
|
|
Result RestGraphHandler::edgeSetsAction(Graph& graph) {
|
|
switch (request()->requestType()) {
|
|
case RequestType::GET:
|
|
return graphActionReadConfig(graph, TRI_COL_TYPE_EDGE, GraphProperty::EDGES);
|
|
case RequestType::POST:
|
|
return createEdgeDefinition(graph);
|
|
default:;
|
|
}
|
|
return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED};
|
|
}
|
|
|
|
Result RestGraphHandler::edgeSetAction(Graph& graph, const std::string& edgeDefinitionName) {
|
|
switch (request()->requestType()) {
|
|
case RequestType::POST:
|
|
return edgeActionCreate(graph, edgeDefinitionName);
|
|
case RequestType::PUT:
|
|
return editEdgeDefinition(graph, edgeDefinitionName);
|
|
case RequestType::DELETE_REQ:
|
|
return removeEdgeDefinition(graph, edgeDefinitionName);
|
|
default:;
|
|
}
|
|
return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED};
|
|
}
|
|
|
|
Result RestGraphHandler::vertexSetAction(Graph& graph, const std::string& vertexCollectionName) {
|
|
switch (request()->requestType()) {
|
|
case RequestType::POST:
|
|
return vertexActionCreate(graph, vertexCollectionName);
|
|
case RequestType::DELETE_REQ:
|
|
return modifyVertexDefinition(graph, VertexDefinitionAction::REMOVE, vertexCollectionName);
|
|
default:;
|
|
}
|
|
return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED};
|
|
}
|
|
|
|
Result RestGraphHandler::vertexAction(Graph& graph, const std::string& vertexCollectionName,
|
|
const std::string& vertexKey) {
|
|
switch (request()->requestType()) {
|
|
case RequestType::GET: {
|
|
vertexActionRead(graph, vertexCollectionName, vertexKey);
|
|
return {TRI_ERROR_NO_ERROR};
|
|
}
|
|
case RequestType::PATCH:
|
|
return vertexActionUpdate(graph, vertexCollectionName, vertexKey);
|
|
case RequestType::PUT:
|
|
return vertexActionReplace(graph, vertexCollectionName, vertexKey);
|
|
case RequestType::DELETE_REQ:
|
|
return vertexActionRemove(graph, vertexCollectionName, vertexKey);
|
|
default:;
|
|
}
|
|
return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED};
|
|
}
|
|
|
|
Result RestGraphHandler::edgeAction(Graph& graph, const std::string& edgeDefinitionName,
|
|
const std::string& edgeKey) {
|
|
switch (request()->requestType()) {
|
|
case RequestType::GET:
|
|
edgeActionRead(graph, edgeDefinitionName, edgeKey);
|
|
return {TRI_ERROR_NO_ERROR};
|
|
case RequestType::DELETE_REQ:
|
|
return edgeActionRemove(graph, edgeDefinitionName, edgeKey);
|
|
case RequestType::PATCH:
|
|
return edgeActionUpdate(graph, edgeDefinitionName, edgeKey);
|
|
break;
|
|
case RequestType::PUT:
|
|
return edgeActionReplace(graph, edgeDefinitionName, edgeKey);
|
|
default:;
|
|
}
|
|
return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED};
|
|
}
|
|
|
|
void RestGraphHandler::vertexActionRead(Graph& graph, std::string const& collectionName,
|
|
std::string const& key) {
|
|
// check for an etag
|
|
bool isValidRevision;
|
|
TRI_voc_rid_t ifNoneRid = extractRevision("if-none-match", isValidRevision);
|
|
if (!isValidRevision) {
|
|
ifNoneRid = UINT64_MAX; // an impossible rev, so precondition failed will happen
|
|
}
|
|
|
|
auto maybeRev = handleRevision();
|
|
|
|
GraphOperations gops{graph, _vocbase};
|
|
OperationResult result = gops.getVertex(collectionName, key, maybeRev);
|
|
|
|
if (!result.ok()) {
|
|
if (result.is(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND)) {
|
|
generateDocumentNotFound(collectionName, key);
|
|
} else if (maybeRev && result.is(TRI_ERROR_ARANGO_CONFLICT)) {
|
|
generatePreconditionFailed(result.slice());
|
|
} else {
|
|
generateTransactionError(collectionName, result.result, key);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ifNoneRid != 0) {
|
|
TRI_voc_rid_t const rid = TRI_ExtractRevisionId(result.slice());
|
|
if (ifNoneRid == rid) {
|
|
generateNotModified(rid);
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
// use default options
|
|
generateVertexRead(result.slice(), *ctx->getVPackOptionsForDump());
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, vertex }
|
|
void RestGraphHandler::generateVertexRead(VPackSlice vertex, VPackOptions const& options) {
|
|
vertex = vertex.resolveExternal();
|
|
resetResponse(rest::ResponseCode::OK);
|
|
addEtagHeader(vertex.get(StaticStrings::RevString));
|
|
generateResultWithField("vertex", vertex, options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, edge }
|
|
void RestGraphHandler::generateEdgeRead(VPackSlice edge, VPackOptions const& options) {
|
|
edge = edge.resolveExternal();
|
|
resetResponse(rest::ResponseCode::OK);
|
|
addEtagHeader(edge.get(StaticStrings::RevString));
|
|
generateResultWithField("edge", edge, options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, removed, old? }
|
|
/// "old" is omitted if old is a NoneSlice.
|
|
void RestGraphHandler::generateRemoved(bool removed, bool wasSynchronous,
|
|
VPackSlice old, VPackOptions const& options) {
|
|
ResponseCode code;
|
|
if (wasSynchronous) {
|
|
code = rest::ResponseCode::OK;
|
|
} else {
|
|
code = rest::ResponseCode::ACCEPTED;
|
|
}
|
|
resetResponse(code);
|
|
VPackBuilder obj;
|
|
obj.add(VPackValue(VPackValueType::Object, true));
|
|
obj.add("removed", VPackValue(removed));
|
|
if (!old.isNone()) {
|
|
obj.add("old", old);
|
|
}
|
|
obj.close();
|
|
generateResultMergedWithObject(obj.slice(), options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, removed, old? }
|
|
/// "old" is omitted if old is a NoneSlice.
|
|
void RestGraphHandler::generateGraphRemoved(bool removed, bool wasSynchronous,
|
|
VPackOptions const& options) {
|
|
ResponseCode code = rest::ResponseCode::ACCEPTED;
|
|
#if 0
|
|
// TODO fix this in a major release upgrade
|
|
if (wasSynchronous) {
|
|
code = rest::ResponseCode::CREATED;
|
|
}
|
|
#endif
|
|
resetResponse(code);
|
|
VPackBuilder obj;
|
|
obj.add(VPackValue(VPackValueType::Object, true));
|
|
obj.add("removed", VPackValue(removed));
|
|
obj.close();
|
|
generateResultMergedWithObject(obj.slice(), options);
|
|
}
|
|
|
|
void RestGraphHandler::generateGraphConfig(VPackSlice slice, VPackOptions const& options) {
|
|
resetResponse(rest::ResponseCode::OK);
|
|
generateResultMergedWithObject(slice, options);
|
|
}
|
|
|
|
void RestGraphHandler::generateCreatedGraphConfig(bool wasSynchronous, VPackSlice slice,
|
|
VPackOptions const& options) {
|
|
ResponseCode code;
|
|
if (wasSynchronous) {
|
|
code = rest::ResponseCode::CREATED;
|
|
} else {
|
|
code = rest::ResponseCode::ACCEPTED;
|
|
}
|
|
resetResponse(code);
|
|
addEtagHeader(slice.get("graph").get(StaticStrings::RevString));
|
|
generateResultMergedWithObject(slice, options);
|
|
}
|
|
|
|
void RestGraphHandler::generateCreatedEdgeDefinition(bool wasSynchronous, VPackSlice slice,
|
|
VPackOptions const& options) {
|
|
ResponseCode code = rest::ResponseCode::ACCEPTED;
|
|
#if 0
|
|
// TODO: fix this in a major upgrade release
|
|
if (wasSynchronous) {
|
|
code = rest::ResponseCode::CREATED;
|
|
}
|
|
#endif
|
|
resetResponse(code);
|
|
addEtagHeader(slice.get("graph").get(StaticStrings::RevString));
|
|
generateResultMergedWithObject(slice, options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, vertex, old?, new? }
|
|
void RestGraphHandler::generateVertexModified(bool wasSynchronous, VPackSlice resultSlice,
|
|
const velocypack::Options& options) {
|
|
generateModified(TRI_COL_TYPE_DOCUMENT, wasSynchronous, resultSlice, options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, vertex }
|
|
void RestGraphHandler::generateVertexCreated(bool wasSynchronous, VPackSlice resultSlice,
|
|
const velocypack::Options& options) {
|
|
generateCreated(TRI_COL_TYPE_DOCUMENT, wasSynchronous, resultSlice, options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, edge, old?, new? }
|
|
void RestGraphHandler::generateEdgeModified(bool wasSynchronous, VPackSlice resultSlice,
|
|
const velocypack::Options& options) {
|
|
generateModified(TRI_COL_TYPE_EDGE, wasSynchronous, resultSlice, options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, edge }
|
|
void RestGraphHandler::generateEdgeCreated(bool wasSynchronous, VPackSlice resultSlice,
|
|
const velocypack::Options& options) {
|
|
generateCreated(TRI_COL_TYPE_EDGE, wasSynchronous, resultSlice, options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, vertex/edge, old?, new? }
|
|
// TODO Maybe a class enum in Graph.h to discern Vertex/Edge is better than
|
|
// abusing document/edge collection types?
|
|
void RestGraphHandler::generateModified(TRI_col_type_e colType,
|
|
bool wasSynchronous, VPackSlice resultSlice,
|
|
const velocypack::Options& options) {
|
|
TRI_ASSERT(colType == TRI_COL_TYPE_DOCUMENT || colType == TRI_COL_TYPE_EDGE);
|
|
if (wasSynchronous) {
|
|
resetResponse(rest::ResponseCode::OK);
|
|
} else {
|
|
resetResponse(rest::ResponseCode::ACCEPTED);
|
|
}
|
|
addEtagHeader(resultSlice.get(StaticStrings::RevString));
|
|
|
|
const char* objectTypeName = "_";
|
|
if (colType == TRI_COL_TYPE_DOCUMENT) {
|
|
objectTypeName = "vertex";
|
|
} else if (colType == TRI_COL_TYPE_EDGE) {
|
|
objectTypeName = "edge";
|
|
}
|
|
|
|
VPackBuilder objectBuilder =
|
|
VPackCollection::remove(resultSlice,
|
|
std::unordered_set<std::string>{"old", "new"});
|
|
// Note: This doesn't really contain the object, only _id, _key, _rev, _oldRev
|
|
VPackSlice objectSlice = objectBuilder.slice();
|
|
VPackSlice oldSlice = resultSlice.get("old");
|
|
VPackSlice newSlice = resultSlice.get("new");
|
|
|
|
VPackBuilder obj;
|
|
obj.add(VPackValue(VPackValueType::Object, true));
|
|
obj.add(objectTypeName, objectSlice);
|
|
if (!oldSlice.isNone()) {
|
|
obj.add("old", oldSlice);
|
|
}
|
|
if (!newSlice.isNone()) {
|
|
obj.add("new", newSlice);
|
|
}
|
|
obj.close();
|
|
generateResultMergedWithObject(obj.slice(), options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, vertex/edge }
|
|
// TODO Maybe a class enum in Graph.h to discern Vertex/Edge is better than
|
|
// abusing document/edge collection types?
|
|
void RestGraphHandler::generateCreated(TRI_col_type_e colType,
|
|
bool wasSynchronous, VPackSlice resultSlice,
|
|
const velocypack::Options& options) {
|
|
TRI_ASSERT(colType == TRI_COL_TYPE_DOCUMENT || colType == TRI_COL_TYPE_EDGE);
|
|
if (wasSynchronous) {
|
|
resetResponse(rest::ResponseCode::CREATED);
|
|
} else {
|
|
resetResponse(rest::ResponseCode::ACCEPTED);
|
|
}
|
|
addEtagHeader(resultSlice.get(StaticStrings::RevString));
|
|
|
|
const char* objectTypeName = "_";
|
|
if (colType == TRI_COL_TYPE_DOCUMENT) {
|
|
objectTypeName = "vertex";
|
|
} else if (colType == TRI_COL_TYPE_EDGE) {
|
|
objectTypeName = "edge";
|
|
}
|
|
|
|
VPackBuilder objectBuilder =
|
|
VPackCollection::remove(resultSlice,
|
|
std::unordered_set<std::string>{"old", "new"});
|
|
// Note: This doesn't really contain the object, only _id, _key, _rev, _oldRev
|
|
VPackSlice objectSlice = objectBuilder.slice();
|
|
VPackSlice newSlice = resultSlice.get("new");
|
|
|
|
VPackBuilder obj;
|
|
obj.add(VPackValue(VPackValueType::Object, true));
|
|
obj.add(objectTypeName, objectSlice);
|
|
if (!newSlice.isNone()) {
|
|
obj.add("new", newSlice);
|
|
}
|
|
obj.close();
|
|
generateResultMergedWithObject(obj.slice(), options);
|
|
}
|
|
|
|
/// @brief generate response object: { error, code, key: value }
|
|
void RestGraphHandler::generateResultWithField(std::string const& key, VPackSlice value,
|
|
VPackOptions const& options) {
|
|
VPackBuilder obj;
|
|
obj.add(VPackValue(VPackValueType::Object, true));
|
|
obj.add(key, value);
|
|
obj.close();
|
|
generateResultMergedWithObject(obj.slice(), options);
|
|
}
|
|
|
|
/// @brief generate response object: MERGE({ error, code }, obj)
|
|
void RestGraphHandler::generateResultMergedWithObject(VPackSlice obj,
|
|
VPackOptions const& options) {
|
|
_response->setContentType(_request->contentTypeResponse());
|
|
|
|
try {
|
|
VPackBuilder result;
|
|
result.add(VPackValue(VPackValueType::Object, true));
|
|
result.add(StaticStrings::Error, VPackValue(false));
|
|
result.add(StaticStrings::Code,
|
|
VPackValue(static_cast<int>(_response->responseCode())));
|
|
result.close();
|
|
VPackBuilder merged = VelocyPackHelper::merge(result.slice(), obj, false, false);
|
|
|
|
writeResult(std::move(*merged.buffer().get()), options);
|
|
} catch (...) {
|
|
// Building the error response failed
|
|
generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_INTERNAL,
|
|
"cannot generate output");
|
|
}
|
|
}
|
|
|
|
// TODO this is nearly exactly the same as vertexActionRead. reuse somehow?
|
|
void RestGraphHandler::edgeActionRead(Graph& graph, const std::string& definitionName,
|
|
const std::string& key) {
|
|
// check for an etag
|
|
bool isValidRevision;
|
|
TRI_voc_rid_t ifNoneRid = extractRevision("if-none-match", isValidRevision);
|
|
if (!isValidRevision) {
|
|
ifNoneRid = UINT64_MAX; // an impossible rev, so precondition failed will happen
|
|
}
|
|
|
|
auto maybeRev = handleRevision();
|
|
|
|
GraphOperations gops{graph, _vocbase};
|
|
OperationResult result = gops.getEdge(definitionName, key, maybeRev);
|
|
|
|
if (result.fail()) {
|
|
generateTransactionError(result);
|
|
return;
|
|
}
|
|
|
|
if (ifNoneRid != 0) {
|
|
TRI_voc_rid_t const rid = TRI_ExtractRevisionId(result.slice());
|
|
if (ifNoneRid == rid) {
|
|
generateNotModified(rid);
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
generateEdgeRead(result.slice(), *ctx->getVPackOptionsForDump());
|
|
}
|
|
|
|
std::unique_ptr<Graph> RestGraphHandler::getGraph(const std::string& graphName) {
|
|
auto graphResult = _gmngr.lookupGraphByName(graphName);
|
|
if (graphResult.fail()) {
|
|
THROW_ARANGO_EXCEPTION(std::move(graphResult).result());
|
|
}
|
|
TRI_ASSERT(graphResult.get() != nullptr);
|
|
return std::move(graphResult.get());
|
|
}
|
|
|
|
// TODO this is very similar to (edge|vertex)ActionRead. find a way to reduce
|
|
// the duplicate code.
|
|
// TODO The tests check that, if "returnOld: true" is passed, the result
|
|
// contains the old value in the field "old". This is not documented in
|
|
// HTTP/Gharial!
|
|
Result RestGraphHandler::edgeActionRemove(Graph& graph, const std::string& definitionName,
|
|
const std::string& key) {
|
|
bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false);
|
|
|
|
bool returnOld = _request->parsedValue(StaticStrings::ReturnOldString, false);
|
|
|
|
auto maybeRev = handleRevision();
|
|
|
|
GraphOperations gops{graph, _vocbase};
|
|
|
|
OperationResult result =
|
|
gops.removeEdge(definitionName, key, maybeRev, waitForSync, returnOld);
|
|
|
|
if (result.fail()) {
|
|
generateTransactionError(result);
|
|
return result.result;
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
generateRemoved(true, result._options.waitForSync, result.slice().get("old"),
|
|
*ctx->getVPackOptionsForDump());
|
|
|
|
return Result();
|
|
}
|
|
|
|
/// @brief If rev is a string, set the Etag header to its value.
|
|
/// rev is expected to be either None or a string.
|
|
void RestGraphHandler::addEtagHeader(velocypack::Slice rev) {
|
|
TRI_ASSERT(rev.isString() || rev.isNone());
|
|
if (rev.isString()) {
|
|
_response->setHeaderNC(StaticStrings::Etag, rev.copyString());
|
|
}
|
|
}
|
|
|
|
Result RestGraphHandler::vertexActionUpdate(graph::Graph& graph,
|
|
const std::string& collectionName,
|
|
const std::string& key) {
|
|
return vertexModify(graph, collectionName, key, true);
|
|
}
|
|
|
|
Result RestGraphHandler::vertexActionReplace(graph::Graph& graph,
|
|
const std::string& collectionName,
|
|
const std::string& key) {
|
|
return vertexModify(graph, collectionName, key, false);
|
|
}
|
|
|
|
Result RestGraphHandler::vertexActionCreate(graph::Graph& graph,
|
|
const std::string& collectionName) {
|
|
return vertexCreate(graph, collectionName);
|
|
}
|
|
|
|
Result RestGraphHandler::edgeActionUpdate(graph::Graph& graph, const std::string& collectionName,
|
|
const std::string& key) {
|
|
return edgeModify(graph, collectionName, key, true);
|
|
}
|
|
|
|
Result RestGraphHandler::edgeActionReplace(graph::Graph& graph, const std::string& collectionName,
|
|
const std::string& key) {
|
|
return edgeModify(graph, collectionName, key, false);
|
|
}
|
|
|
|
Result RestGraphHandler::edgeModify(graph::Graph& graph, const std::string& collectionName,
|
|
const std::string& key, bool isPatch) {
|
|
return documentModify(graph, collectionName, key, isPatch, TRI_COL_TYPE_EDGE);
|
|
}
|
|
|
|
Result RestGraphHandler::edgeCreate(graph::Graph& graph, const std::string& collectionName) {
|
|
return documentCreate(graph, collectionName, TRI_COL_TYPE_EDGE);
|
|
}
|
|
|
|
Result RestGraphHandler::edgeActionCreate(graph::Graph& graph, const std::string& collectionName) {
|
|
return edgeCreate(graph, collectionName);
|
|
}
|
|
|
|
Result RestGraphHandler::vertexModify(graph::Graph& graph, const std::string& collectionName,
|
|
const std::string& key, bool isPatch) {
|
|
return documentModify(graph, collectionName, key, isPatch, TRI_COL_TYPE_DOCUMENT);
|
|
}
|
|
|
|
Result RestGraphHandler::vertexCreate(graph::Graph& graph, const std::string& collectionName) {
|
|
return documentCreate(graph, collectionName, TRI_COL_TYPE_DOCUMENT);
|
|
}
|
|
|
|
// /_api/gharial/{graph-name}/edge/{definition-name}
|
|
Result RestGraphHandler::editEdgeDefinition(graph::Graph& graph,
|
|
const std::string& edgeDefinitionName) {
|
|
return modifyEdgeDefinition(graph, EdgeDefinitionAction::EDIT, edgeDefinitionName);
|
|
}
|
|
|
|
Result RestGraphHandler::createEdgeDefinition(graph::Graph& graph) {
|
|
return modifyEdgeDefinition(graph, EdgeDefinitionAction::CREATE);
|
|
}
|
|
|
|
// /_api/gharial/{graph-name}/edge
|
|
Result RestGraphHandler::modifyEdgeDefinition(graph::Graph& graph, EdgeDefinitionAction action,
|
|
std::string edgeDefinitionName) {
|
|
// edgeDefinitionName == "" <=> action == CREATE
|
|
TRI_ASSERT((action == EdgeDefinitionAction::CREATE) == edgeDefinitionName.empty());
|
|
bool parseSuccess = false;
|
|
VPackSlice body = this->parseVPackBody(parseSuccess);
|
|
if (!parseSuccess) {
|
|
return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"};
|
|
}
|
|
bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false);
|
|
bool dropCollections = _request->parsedValue(StaticStrings::GraphDropCollections, false);
|
|
|
|
GraphOperations gops{graph, _vocbase};
|
|
OperationResult result;
|
|
|
|
if (action == EdgeDefinitionAction::CREATE) {
|
|
result = gops.addEdgeDefinition(body, waitForSync);
|
|
} else if (action == EdgeDefinitionAction::EDIT) {
|
|
result = gops.editEdgeDefinition(body, waitForSync, edgeDefinitionName);
|
|
} else if (action == EdgeDefinitionAction::REMOVE) {
|
|
// TODO Does this get waitForSync? Not according to the documentation.
|
|
// if not, remove the parameter from eraseEdgeDefinition. What about
|
|
// add/edit?
|
|
result = gops.eraseEdgeDefinition(waitForSync, edgeDefinitionName, dropCollections);
|
|
} else {
|
|
TRI_ASSERT(false);
|
|
}
|
|
|
|
if (result.fail()) {
|
|
generateTransactionError(result);
|
|
return result.result;
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
|
|
auto newGraph = getGraph(graph.name());
|
|
TRI_ASSERT(newGraph != nullptr);
|
|
VPackBuilder builder;
|
|
builder.openObject();
|
|
newGraph->graphForClient(builder);
|
|
builder.close();
|
|
|
|
generateCreatedEdgeDefinition(waitForSync, builder.slice(),
|
|
*ctx->getVPackOptionsForDump());
|
|
|
|
return Result();
|
|
}
|
|
|
|
Result RestGraphHandler::modifyVertexDefinition(graph::Graph& graph,
|
|
VertexDefinitionAction action,
|
|
std::string vertexDefinitionName) {
|
|
bool parseSuccess = false;
|
|
VPackSlice body = this->parseVPackBody(parseSuccess);
|
|
if (!parseSuccess) {
|
|
return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"};
|
|
}
|
|
|
|
// TODO maybe merge this function with modifyEdgeDefinition?
|
|
bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false);
|
|
bool dropCollection = _request->parsedValue(StaticStrings::GraphDropCollection, false);
|
|
bool createCollection =
|
|
_request->parsedValue(StaticStrings::GraphCreateCollection, true);
|
|
|
|
GraphOperations gops{graph, _vocbase};
|
|
OperationResult result;
|
|
|
|
if (action == VertexDefinitionAction::CREATE) {
|
|
result = gops.addOrphanCollection(body, waitForSync, createCollection);
|
|
} else if (action == VertexDefinitionAction::REMOVE) {
|
|
result = gops.eraseOrphanCollection(waitForSync, vertexDefinitionName, dropCollection);
|
|
} else {
|
|
TRI_ASSERT(false);
|
|
}
|
|
|
|
if (result.fail()) {
|
|
generateTransactionError(result);
|
|
return result.result;
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
|
|
auto newGraph = getGraph(graph.name());
|
|
TRI_ASSERT(newGraph != nullptr);
|
|
VPackBuilder builder;
|
|
builder.openObject();
|
|
newGraph->graphForClient(builder);
|
|
builder.close();
|
|
|
|
generateCreatedEdgeDefinition(waitForSync, builder.slice(),
|
|
*ctx->getVPackOptionsForDump());
|
|
|
|
return Result();
|
|
}
|
|
|
|
Result RestGraphHandler::removeEdgeDefinition(graph::Graph& graph,
|
|
const std::string& edgeDefinitionName) {
|
|
return modifyEdgeDefinition(graph, EdgeDefinitionAction::REMOVE, edgeDefinitionName);
|
|
}
|
|
|
|
// TODO The tests check that, if "returnOld: true" is passed, the result
|
|
// contains the old value in the field "old"; and if "returnNew: true" is
|
|
// passed, the field "new" contains the new value (along with "vertex"!).
|
|
// This is not documented in HTTP/Gharial!
|
|
// TODO the document API also supports mergeObjects, silent and ignoreRevs;
|
|
// should gharial, too?
|
|
Result RestGraphHandler::documentModify(graph::Graph& graph, const std::string& collectionName,
|
|
const std::string& key, bool isPatch,
|
|
TRI_col_type_e colType) {
|
|
bool parseSuccess = false;
|
|
VPackSlice body = this->parseVPackBody(parseSuccess);
|
|
if (!parseSuccess) {
|
|
return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"};
|
|
}
|
|
|
|
bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false);
|
|
bool returnNew = _request->parsedValue(StaticStrings::ReturnNewString, false);
|
|
bool returnOld = _request->parsedValue(StaticStrings::ReturnOldString, false);
|
|
// Note: the default here differs from the one in the RestDoumentHandler
|
|
bool keepNull = _request->parsedValue(StaticStrings::KeepNullString, true);
|
|
|
|
// extract the revision, if single document variant and header given:
|
|
std::unique_ptr<VPackBuilder> builder;
|
|
auto maybeRev = handleRevision();
|
|
|
|
GraphOperations gops{graph, _vocbase};
|
|
|
|
OperationResult result;
|
|
// TODO get rid of this branching, rather use several functions and reuse the
|
|
// common code another way.
|
|
if (isPatch && colType == TRI_COL_TYPE_DOCUMENT) {
|
|
result = gops.updateVertex(collectionName, key, body, maybeRev, waitForSync,
|
|
returnOld, returnNew, keepNull);
|
|
} else if (!isPatch && colType == TRI_COL_TYPE_DOCUMENT) {
|
|
result = gops.replaceVertex(collectionName, key, body, maybeRev,
|
|
waitForSync, returnOld, returnNew, keepNull);
|
|
} else if (isPatch && colType == TRI_COL_TYPE_EDGE) {
|
|
result = gops.updateEdge(collectionName, key, body, maybeRev, waitForSync,
|
|
returnOld, returnNew, keepNull);
|
|
} else if (!isPatch && colType == TRI_COL_TYPE_EDGE) {
|
|
result = gops.replaceEdge(collectionName, key, body, maybeRev, waitForSync,
|
|
returnOld, returnNew, keepNull);
|
|
} else {
|
|
TRI_ASSERT(false);
|
|
}
|
|
|
|
if (result.fail()) {
|
|
generateTransactionError(result);
|
|
return result.result;
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
switch (colType) {
|
|
case TRI_COL_TYPE_DOCUMENT:
|
|
generateVertexModified(result._options.waitForSync, result.slice(),
|
|
*ctx->getVPackOptionsForDump());
|
|
break;
|
|
case TRI_COL_TYPE_EDGE:
|
|
generateEdgeModified(result._options.waitForSync, result.slice(),
|
|
*ctx->getVPackOptionsForDump());
|
|
break;
|
|
default:
|
|
TRI_ASSERT(false);
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
Result RestGraphHandler::documentCreate(graph::Graph& graph, const std::string& collectionName,
|
|
TRI_col_type_e colType) {
|
|
bool parseSuccess = false;
|
|
VPackSlice body = this->parseVPackBody(parseSuccess);
|
|
if (!parseSuccess) {
|
|
return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"};
|
|
}
|
|
|
|
bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false);
|
|
bool returnNew = _request->parsedValue(StaticStrings::ReturnNewString, false);
|
|
|
|
GraphOperations gops{graph, _vocbase};
|
|
|
|
OperationResult result;
|
|
if (colType == TRI_COL_TYPE_DOCUMENT) {
|
|
result = gops.createVertex(collectionName, body, waitForSync, returnNew);
|
|
} else if (colType == TRI_COL_TYPE_EDGE) {
|
|
result = gops.createEdge(collectionName, body, waitForSync, returnNew);
|
|
} else {
|
|
TRI_ASSERT(false);
|
|
}
|
|
|
|
if (result.fail()) {
|
|
// need to call more detailed constructor here
|
|
generateTransactionError(collectionName, result, "", 0);
|
|
return result.result;
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
switch (colType) {
|
|
case TRI_COL_TYPE_DOCUMENT:
|
|
generateVertexCreated(result._options.waitForSync, result.slice(),
|
|
*ctx->getVPackOptionsForDump());
|
|
break;
|
|
case TRI_COL_TYPE_EDGE:
|
|
generateEdgeCreated(result._options.waitForSync, result.slice(),
|
|
*ctx->getVPackOptionsForDump());
|
|
break;
|
|
default:
|
|
TRI_ASSERT(false);
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
Result RestGraphHandler::vertexActionRemove(graph::Graph& graph,
|
|
const std::string& collectionName,
|
|
const std::string& key) {
|
|
bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false);
|
|
|
|
bool returnOld = _request->parsedValue(StaticStrings::ReturnOldString, false);
|
|
|
|
auto maybeRev = handleRevision();
|
|
|
|
GraphOperations gops{graph, _vocbase};
|
|
|
|
OperationResult result =
|
|
gops.removeVertex(collectionName, key, maybeRev, waitForSync, returnOld);
|
|
|
|
if (result.fail()) {
|
|
generateTransactionError(result);
|
|
return result.result;
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
generateRemoved(true, result._options.waitForSync, result.slice().get("old"),
|
|
*ctx->getVPackOptionsForDump());
|
|
|
|
return Result();
|
|
}
|
|
|
|
Result RestGraphHandler::graphActionReadGraphConfig(graph::Graph const& graph) {
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
VPackBuilder builder;
|
|
builder.openObject();
|
|
graph.graphForClient(builder);
|
|
builder.close();
|
|
generateGraphConfig(builder.slice(), *ctx->getVPackOptionsForDump());
|
|
|
|
return Result();
|
|
}
|
|
|
|
Result RestGraphHandler::graphActionRemoveGraph(graph::Graph const& graph) {
|
|
bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false);
|
|
bool dropCollections = _request->parsedValue(StaticStrings::GraphDropCollections, false);
|
|
|
|
OperationResult result = _gmngr.removeGraph(graph, waitForSync, dropCollections);
|
|
|
|
if (result.fail()) {
|
|
generateTransactionError(result);
|
|
return result.result;
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
generateGraphRemoved(true, result._options.waitForSync, *ctx->getVPackOptionsForDump());
|
|
|
|
return Result();
|
|
}
|
|
|
|
Result RestGraphHandler::graphActionCreateGraph() {
|
|
bool parseSuccess = false;
|
|
VPackSlice body = this->parseVPackBody(parseSuccess);
|
|
if (!parseSuccess) {
|
|
return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"};
|
|
}
|
|
bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false);
|
|
|
|
{
|
|
OperationResult result = _gmngr.createGraph(body, waitForSync);
|
|
|
|
if (result.fail()) {
|
|
generateTransactionError(result);
|
|
return result.result;
|
|
}
|
|
}
|
|
|
|
std::string graphName = body.get(StaticStrings::DataSourceName).copyString();
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
std::unique_ptr<Graph const> graph = getGraph(graphName);
|
|
TRI_ASSERT(graph != nullptr);
|
|
|
|
VPackBuilder builder;
|
|
builder.openObject();
|
|
graph->graphForClient(builder);
|
|
builder.close();
|
|
|
|
generateCreatedGraphConfig(waitForSync, builder.slice(), *ctx->getVPackOptionsForDump());
|
|
|
|
return Result();
|
|
}
|
|
|
|
Result RestGraphHandler::graphActionReadGraphs() {
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
|
|
VPackBuilder builder;
|
|
_gmngr.readGraphs(builder, arangodb::aql::PART_MAIN);
|
|
|
|
generateGraphConfig(builder.slice(), *ctx->getVPackOptionsForDump());
|
|
|
|
return Result();
|
|
}
|
|
|
|
Result RestGraphHandler::graphActionReadConfig(graph::Graph const& graph, TRI_col_type_e colType,
|
|
GraphProperty property) {
|
|
VPackBuilder builder;
|
|
|
|
if (colType == TRI_COL_TYPE_DOCUMENT && property == GraphProperty::VERTICES) {
|
|
graph.verticesToVpack(builder);
|
|
} else if (colType == TRI_COL_TYPE_EDGE && property == GraphProperty::EDGES) {
|
|
graph.edgesToVpack(builder);
|
|
} else {
|
|
TRI_ASSERT(false);
|
|
}
|
|
|
|
auto ctx = std::make_shared<transaction::StandaloneContext>(_vocbase);
|
|
|
|
generateGraphConfig(builder.slice(), *ctx->getVPackOptionsForDump());
|
|
|
|
return Result();
|
|
}
|
|
|
|
RequestLane RestGraphHandler::lane() const { return RequestLane::CLIENT_SLOW; }
|
|
|
|
boost::optional<TRI_voc_rid_t> RestGraphHandler::handleRevision() const {
|
|
bool isValidRevision;
|
|
TRI_voc_rid_t revision = extractRevision("if-match", isValidRevision);
|
|
if (!isValidRevision) {
|
|
revision = UINT64_MAX; // an impossible revision, so precondition failed
|
|
}
|
|
if (revision == 0 || revision == UINT64_MAX) {
|
|
bool found = false;
|
|
std::string const& revString = _request->value("rev", found);
|
|
if (found) {
|
|
revision = TRI_StringToRid(revString.data(), revString.size(), false);
|
|
}
|
|
}
|
|
return boost::make_optional(revision != 0, revision);
|
|
}
|