//////////////////////////////////////////////////////////////////////////////// /// 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 #include #include #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 = 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(_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{"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{"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(_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(_vocbase); generateEdgeRead(result.slice(), *ctx->getVPackOptionsForDump()); } std::unique_ptr 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(_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(_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(_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 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(_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(_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(_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(_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(_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(_vocbase); std::unique_ptr 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(_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(_vocbase); generateGraphConfig(builder.slice(), *ctx->getVPackOptionsForDump()); return Result(); } RequestLane RestGraphHandler::lane() const { return RequestLane::CLIENT_SLOW; } boost::optional 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); }