From f1ca1d11ff30d1bb949f21f46d19966b2e0f85eb Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 2 Sep 2019 16:53:00 +0200 Subject: [PATCH] Bug fix/make graphs transaction aware (#9840) --- arangod/Aql/RestAqlHandler.cpp | 4 +- arangod/Graph/GraphOperations.cpp | 18 +- arangod/Graph/GraphOperations.h | 8 +- arangod/RestHandler/RestCursorHandler.cpp | 4 +- arangod/RestHandler/RestGraphHandler.cpp | 64 +- .../RestHandler/RestVocbaseBaseHandler.cpp | 10 +- arangod/RestHandler/RestVocbaseBaseHandler.h | 336 +++----- arangod/V8Server/v8-general-graph.cpp | 19 +- .../client/shell/shell-graph-transaction.js | 759 ++++++++++++++++++ 9 files changed, 923 insertions(+), 299 deletions(-) create mode 100644 tests/js/client/shell/shell-graph-transaction.js diff --git a/arangod/Aql/RestAqlHandler.cpp b/arangod/Aql/RestAqlHandler.cpp index 883cfd7a3b..df88668989 100644 --- a/arangod/Aql/RestAqlHandler.cpp +++ b/arangod/Aql/RestAqlHandler.cpp @@ -229,8 +229,8 @@ void RestAqlHandler::setupClusterQuery() { } collectionBuilder.close(); - // creates a StandaloneContext or a leasing context - auto ctx = createAQLTransactionContext(); + // creates a StandaloneContext or a leased context + auto ctx = createTransactionContext(); VPackBuilder answerBuilder; answerBuilder.openObject(); diff --git a/arangod/Graph/GraphOperations.cpp b/arangod/Graph/GraphOperations.cpp index cde2616d06..061c5d3977 100644 --- a/arangod/Graph/GraphOperations.cpp +++ b/arangod/Graph/GraphOperations.cpp @@ -54,9 +54,12 @@ using namespace arangodb; using namespace arangodb::graph; -std::shared_ptr GraphOperations::ctx() const { - return transaction::StandaloneContext::Create(_vocbase); -}; +std::shared_ptr GraphOperations::ctx() { + if (!_ctx) { + _ctx = std::make_shared(_vocbase); + } + return _ctx; +} void GraphOperations::checkForUsedEdgeCollections(const Graph& graph, const std::string& collectionName, @@ -459,7 +462,7 @@ OperationResult GraphOperations::addEdgeDefinition(VPackSlice edgeDefinitionSlic // finally save the graph return gmngr.storeGraph(_graph, waitForSync, true); -}; +} // vertices @@ -469,7 +472,7 @@ OperationResult GraphOperations::getVertex(std::string const& collectionName, std::string const& key, boost::optional 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, @@ -870,10 +873,9 @@ OperationResult GraphOperations::removeEdgeOrVertex(const std::string& collectio edgeCollections.emplace(it); // but also to edgeCollections for later iteration } - auto ctx = std::make_shared(_vocbase); transaction::Options trxOptions; trxOptions.waitForSync = waitForSync; - transaction::Methods trx{ctx, {}, trxCollections, {}, trxOptions}; + transaction::Methods trx{ctx(), {}, trxCollections, {}, trxOptions}; res = trx.begin(); @@ -901,7 +903,7 @@ OperationResult GraphOperations::removeEdgeOrVertex(const std::string& collectio arangodb::aql::Query query(false, _vocbase, queryString, bindVars, nullptr, arangodb::aql::PART_DEPENDENT); - query.setTransactionContext(ctx); // hack to share the same transaction + query.setTransactionContext(ctx()); // hack to share the same transaction auto queryResult = query.executeSync(QueryRegistryFeature::registry()); diff --git a/arangod/Graph/GraphOperations.h b/arangod/Graph/GraphOperations.h index d38322b830..2327a916bb 100644 --- a/arangod/Graph/GraphOperations.h +++ b/arangod/Graph/GraphOperations.h @@ -48,14 +48,16 @@ class GraphOperations { private: Graph& _graph; TRI_vocbase_t& _vocbase; + std::shared_ptr _ctx; Graph const& graph() const { return _graph; }; - std::shared_ptr ctx() const; + std::shared_ptr ctx(); public: GraphOperations() = delete; - GraphOperations(Graph& graph_, TRI_vocbase_t& vocbase) - : _graph(graph_), _vocbase(vocbase) {} + GraphOperations(Graph& graph_, TRI_vocbase_t& vocbase, + std::shared_ptr const& ctx = nullptr) + : _graph(graph_), _vocbase(vocbase), _ctx(ctx) {} // TODO I added the complex result type for the get* methods to exactly // reproduce (in the RestGraphHandler) the behavior of the similar methods diff --git a/arangod/RestHandler/RestCursorHandler.cpp b/arangod/RestHandler/RestCursorHandler.cpp index ade74f6cf0..437804b876 100644 --- a/arangod/RestHandler/RestCursorHandler.cpp +++ b/arangod/RestHandler/RestCursorHandler.cpp @@ -215,7 +215,7 @@ RestStatus RestCursorHandler::registerQueryOrCursor(VPackSlice const& slice) { Cursor* cursor = cursors->createQueryStream(querySlice.copyString(), bindVarsBuilder, _options, batchSize, ttl, /*contextOwnedByExt*/ false, - createAQLTransactionContext()); + createTransactionContext()); return generateCursorResult(rest::ResponseCode::CREATED, cursor); } @@ -230,7 +230,7 @@ RestStatus RestCursorHandler::registerQueryOrCursor(VPackSlice const& slice) { auto query = std::make_unique( false, _vocbase, arangodb::aql::QueryString(queryStr, static_cast(l)), bindVarsBuilder, _options, arangodb::aql::PART_MAIN); - query->setTransactionContext(createAQLTransactionContext()); + query->setTransactionContext(createTransactionContext()); std::shared_ptr ss = query->sharedState(); ss->setContinueHandler([self = shared_from_this(), ss] { self->continueHandlerExecution(); }); diff --git a/arangod/RestHandler/RestGraphHandler.cpp b/arangod/RestHandler/RestGraphHandler.cpp index b30e8d04a7..77743eee6d 100644 --- a/arangod/RestHandler/RestGraphHandler.cpp +++ b/arangod/RestHandler/RestGraphHandler.cpp @@ -251,7 +251,6 @@ Result RestGraphHandler::edgeAction(Graph& graph, const std::string& edgeDefinit return edgeActionRemove(graph, edgeDefinitionName, edgeKey); case RequestType::PATCH: return edgeActionUpdate(graph, edgeDefinitionName, edgeKey); - break; case RequestType::PUT: return edgeActionReplace(graph, edgeDefinitionName, edgeKey); default:; @@ -270,7 +269,8 @@ void RestGraphHandler::vertexActionRead(Graph& graph, std::string const& collect auto maybeRev = handleRevision(); - GraphOperations gops{graph, _vocbase}; + auto ctx = createTransactionContext(); + GraphOperations gops{graph, _vocbase, ctx}; OperationResult result = gops.getVertex(collectionName, key, maybeRev); if (!result.ok()) { @@ -292,7 +292,6 @@ void RestGraphHandler::vertexActionRead(Graph& graph, std::string const& collect } } - auto ctx = std::make_shared(_vocbase); // use default options generateVertexRead(result.slice(), *ctx->getVPackOptionsForDump()); } @@ -533,7 +532,8 @@ void RestGraphHandler::edgeActionRead(Graph& graph, const std::string& definitio auto maybeRev = handleRevision(); - GraphOperations gops{graph, _vocbase}; + auto ctx = createTransactionContext(); + GraphOperations gops{graph, _vocbase, ctx}; OperationResult result = gops.getEdge(definitionName, key, maybeRev); if (result.fail()) { @@ -549,7 +549,6 @@ void RestGraphHandler::edgeActionRead(Graph& graph, const std::string& definitio } } - auto ctx = std::make_shared(_vocbase); generateEdgeRead(result.slice(), *ctx->getVPackOptionsForDump()); } @@ -575,7 +574,8 @@ Result RestGraphHandler::edgeActionRemove(Graph& graph, const std::string& defin auto maybeRev = handleRevision(); - GraphOperations gops{graph, _vocbase}; + auto ctx = createTransactionContext(); + GraphOperations gops{graph, _vocbase, ctx}; OperationResult result = gops.removeEdge(definitionName, key, maybeRev, waitForSync, returnOld); @@ -585,7 +585,6 @@ Result RestGraphHandler::edgeActionRemove(Graph& graph, const std::string& defin return result.result; } - auto ctx = std::make_shared(_vocbase); generateRemoved(true, result._options.waitForSync, result.slice().get("old"), *ctx->getVPackOptionsForDump()); @@ -673,7 +672,8 @@ Result RestGraphHandler::modifyEdgeDefinition(graph::Graph& graph, EdgeDefinitio bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false); bool dropCollections = _request->parsedValue(StaticStrings::GraphDropCollections, false); - GraphOperations gops{graph, _vocbase}; + auto ctx = createTransactionContext(); + GraphOperations gops{graph, _vocbase, ctx}; OperationResult result; if (action == EdgeDefinitionAction::CREATE) { @@ -694,8 +694,6 @@ Result RestGraphHandler::modifyEdgeDefinition(graph::Graph& graph, EdgeDefinitio return result.result; } - auto ctx = std::make_shared(_vocbase); - auto newGraph = getGraph(graph.name()); TRI_ASSERT(newGraph != nullptr); VPackBuilder builder; @@ -724,7 +722,8 @@ Result RestGraphHandler::modifyVertexDefinition(graph::Graph& graph, bool createCollection = _request->parsedValue(StaticStrings::GraphCreateCollection, true); - GraphOperations gops{graph, _vocbase}; + auto ctx = createTransactionContext(); + GraphOperations gops{graph, _vocbase, ctx}; OperationResult result; if (action == VertexDefinitionAction::CREATE) { @@ -740,8 +739,6 @@ Result RestGraphHandler::modifyVertexDefinition(graph::Graph& graph, return result.result; } - auto ctx = std::make_shared(_vocbase); - auto newGraph = getGraph(graph.name()); TRI_ASSERT(newGraph != nullptr); VPackBuilder builder; @@ -785,7 +782,8 @@ Result RestGraphHandler::documentModify(graph::Graph& graph, const std::string& std::unique_ptr builder; auto maybeRev = handleRevision(); - GraphOperations gops{graph, _vocbase}; + auto ctx = createTransactionContext(); + GraphOperations gops{graph, _vocbase, ctx}; OperationResult result; // TODO get rid of this branching, rather use several functions and reuse the @@ -811,7 +809,6 @@ Result RestGraphHandler::documentModify(graph::Graph& graph, const std::string& return result.result; } - auto ctx = std::make_shared(_vocbase); switch (colType) { case TRI_COL_TYPE_DOCUMENT: generateVertexModified(result._options.waitForSync, result.slice(), @@ -828,7 +825,7 @@ Result RestGraphHandler::documentModify(graph::Graph& graph, const std::string& return TRI_ERROR_NO_ERROR; } -Result RestGraphHandler::documentCreate(graph::Graph& graph, const std::string& collectionName, +Result RestGraphHandler::documentCreate(graph::Graph& graph, std::string const& collectionName, TRI_col_type_e colType) { bool parseSuccess = false; VPackSlice body = this->parseVPackBody(parseSuccess); @@ -839,7 +836,8 @@ Result RestGraphHandler::documentCreate(graph::Graph& graph, const std::string& bool waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false); bool returnNew = _request->parsedValue(StaticStrings::ReturnNewString, false); - GraphOperations gops{graph, _vocbase}; + auto ctx = createTransactionContext(); + GraphOperations gops{graph, _vocbase, ctx}; OperationResult result; if (colType == TRI_COL_TYPE_DOCUMENT) { @@ -853,24 +851,22 @@ Result RestGraphHandler::documentCreate(graph::Graph& graph, const std::string& 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(), + } else { + 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; - case TRI_COL_TYPE_EDGE: - generateEdgeCreated(result._options.waitForSync, result.slice(), - *ctx->getVPackOptionsForDump()); - break; - default: - TRI_ASSERT(false); + break; + default: + TRI_ASSERT(false); + } } - return TRI_ERROR_NO_ERROR; + return result.result; } Result RestGraphHandler::vertexActionRemove(graph::Graph& graph, @@ -882,7 +878,8 @@ Result RestGraphHandler::vertexActionRemove(graph::Graph& graph, auto maybeRev = handleRevision(); - GraphOperations gops{graph, _vocbase}; + auto ctx = createTransactionContext(); + GraphOperations gops{graph, _vocbase, ctx}; OperationResult result = gops.removeVertex(collectionName, key, maybeRev, waitForSync, returnOld); @@ -892,7 +889,6 @@ Result RestGraphHandler::vertexActionRemove(graph::Graph& graph, return result.result; } - auto ctx = std::make_shared(_vocbase); generateRemoved(true, result._options.waitForSync, result.slice().get("old"), *ctx->getVPackOptionsForDump()); diff --git a/arangod/RestHandler/RestVocbaseBaseHandler.cpp b/arangod/RestHandler/RestVocbaseBaseHandler.cpp index 678be33ac8..ee5172f504 100644 --- a/arangod/RestHandler/RestVocbaseBaseHandler.cpp +++ b/arangod/RestHandler/RestVocbaseBaseHandler.cpp @@ -547,7 +547,7 @@ void RestVocbaseBaseHandler::extractStringParameter(std::string const& name, } std::unique_ptr RestVocbaseBaseHandler::createTransaction( - std::string const& name, AccessMode::Type type) const { + std::string const& collectionName, AccessMode::Type type) const { bool found = false; std::string value = _request->header(StaticStrings::TransactionId, found); if (found) { @@ -584,15 +584,15 @@ std::unique_ptr RestVocbaseBaseHandler::createTrans LOG_TOPIC("e94ea", DEBUG, Logger::TRANSACTIONS) << "Transaction with id '" << tid << "' not found"; THROW_ARANGO_EXCEPTION(TRI_ERROR_TRANSACTION_NOT_FOUND); } - return std::make_unique(ctx, name, type); + return std::make_unique(ctx, collectionName, type); } else { auto ctx = transaction::StandaloneContext::Create(_vocbase); - return std::make_unique(ctx, name, type); + return std::make_unique(ctx, collectionName, type); } } /// @brief create proper transaction context, inclusing the proper IDs -std::shared_ptr RestVocbaseBaseHandler::createAQLTransactionContext() const { +std::shared_ptr RestVocbaseBaseHandler::createTransactionContext() const { bool found = false; std::string value = _request->header(StaticStrings::TransactionId, found); if (!found) { @@ -631,7 +631,7 @@ std::shared_ptr RestVocbaseBaseHandler::createAQLTransacti } } } - + auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE); if (!ctx) { LOG_TOPIC("2cfed", DEBUG, Logger::TRANSACTIONS) << "Transaction with id '" << tid << "' not found"; diff --git a/arangod/RestHandler/RestVocbaseBaseHandler.h b/arangod/RestHandler/RestVocbaseBaseHandler.h index 70402fc15d..40f281c9d9 100644 --- a/arangod/RestHandler/RestVocbaseBaseHandler.h +++ b/arangod/RestHandler/RestVocbaseBaseHandler.h @@ -39,347 +39,207 @@ namespace arangodb { class SingleCollectionTransaction; class VocbaseContext; -//////////////////////////////////////////////////////////////////////////////// /// @brief abstract base request handler -//////////////////////////////////////////////////////////////////////////////// - class RestVocbaseBaseHandler : public RestBaseHandler { RestVocbaseBaseHandler(RestVocbaseBaseHandler const&) = delete; RestVocbaseBaseHandler& operator=(RestVocbaseBaseHandler const&) = delete; public: - ////////////////////////////////////////////////////////////////////////////// /// @brief agency public path - ////////////////////////////////////////////////////////////////////////////// - static std::string const AGENCY_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief agency private path - ////////////////////////////////////////////////////////////////////////////// - static std::string const AGENCY_PRIV_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief analyzer path - ////////////////////////////////////////////////////////////////////////////// static std::string const ANALYZER_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief batch path - ////////////////////////////////////////////////////////////////////////////// - static std::string const BATCH_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief collection path - ////////////////////////////////////////////////////////////////////////////// - static std::string const COLLECTION_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief control pregel path - ////////////////////////////////////////////////////////////////////////////// - static std::string const CONTROL_PREGEL_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief cursor path - ////////////////////////////////////////////////////////////////////////////// - static std::string const CURSOR_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief database path - ////////////////////////////////////////////////////////////////////////////// - static std::string const DATABASE_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief document path - ////////////////////////////////////////////////////////////////////////////// - static std::string const DOCUMENT_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief edges path - ////////////////////////////////////////////////////////////////////////////// - static std::string const EDGES_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief gharial graph api path - ////////////////////////////////////////////////////////////////////////////// - static std::string const GHARIAL_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief endpoint path - ////////////////////////////////////////////////////////////////////////////// - static std::string const ENDPOINT_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief document import path - ////////////////////////////////////////////////////////////////////////////// - static std::string const IMPORT_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief index path - ////////////////////////////////////////////////////////////////////////////// - static std::string const INDEX_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief replication path - ////////////////////////////////////////////////////////////////////////////// - static std::string const REPLICATION_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief simple query all path - ////////////////////////////////////////////////////////////////////////////// - static std::string const SIMPLE_QUERY_ALL_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief simple query all keys path - ////////////////////////////////////////////////////////////////////////////// - static std::string const SIMPLE_QUERY_ALL_KEYS_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief simple query by example path - ////////////////////////////////////////////////////////////////////////////// - static std::string const SIMPLE_QUERY_BY_EXAMPLE; - ////////////////////////////////////////////////////////////////////////////// /// @brief simple query first example path - ////////////////////////////////////////////////////////////////////////////// - static std::string const SIMPLE_FIRST_EXAMPLE; - ////////////////////////////////////////////////////////////////////////////// /// @brief simple query remove by example path - ////////////////////////////////////////////////////////////////////////////// - static std::string const SIMPLE_REMOVE_BY_EXAMPLE; - ////////////////////////////////////////////////////////////////////////////// /// @brief simple query replace by example path - ////////////////////////////////////////////////////////////////////////////// - static std::string const SIMPLE_REPLACE_BY_EXAMPLE; - ////////////////////////////////////////////////////////////////////////////// /// @brief simple query replace by example path - ////////////////////////////////////////////////////////////////////////////// - static std::string const SIMPLE_UPDATE_BY_EXAMPLE; - ////////////////////////////////////////////////////////////////////////////// /// @brief simple batch document lookup path - ////////////////////////////////////////////////////////////////////////////// - static std::string const SIMPLE_LOOKUP_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief simple batch document removal path - ////////////////////////////////////////////////////////////////////////////// - static std::string const SIMPLE_REMOVE_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief tasks path - ////////////////////////////////////////////////////////////////////////////// - static std::string const TASKS_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief upload path - ////////////////////////////////////////////////////////////////////////////// - static std::string const UPLOAD_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief users path - ////////////////////////////////////////////////////////////////////////////// - static std::string const USERS_PATH; - ////////////////////////////////////////////////////////////////////////////// /// @brief view path - ////////////////////////////////////////////////////////////////////////////// - static std::string const VIEW_PATH; /// @brief Internal Traverser path - static std::string const INTERNAL_TRAVERSER_PATH; public: RestVocbaseBaseHandler(GeneralRequest*, GeneralResponse*); ~RestVocbaseBaseHandler(); - protected: - ////////////////////////////////////////////////////////////////////////////// - /// @brief assemble a document id from a string and a string - /// optionally url-encodes - ////////////////////////////////////////////////////////////////////////////// - - std::string assembleDocumentId(std::string const&, std::string const&, bool); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates a HTTP 201 or 202 response - ////////////////////////////////////////////////////////////////////////////// - - void generate20x(arangodb::OperationResult const&, std::string const&, - TRI_col_type_e, arangodb::velocypack::Options const*, - bool isMultiple, rest::ResponseCode waitForSyncResponseCode); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates message for a saved document - ////////////////////////////////////////////////////////////////////////////// - - void generateSaved(arangodb::OperationResult const& result, - std::string const& collectionName, TRI_col_type_e type, - arangodb::velocypack::Options const*, bool isMultiple); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates deleted message - ////////////////////////////////////////////////////////////////////////////// - - void generateDeleted(arangodb::OperationResult const& result, - std::string const& collectionName, TRI_col_type_e type, - arangodb::velocypack::Options const*, bool isMultiple); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates document not found error message, no transaction info - ////////////////////////////////////////////////////////////////////////////// - - void generateDocumentNotFound(std::string const& /* collection name */, - std::string const& /* document key */) { - generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND); - } - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates not implemented - ////////////////////////////////////////////////////////////////////////////// - - void generateNotImplemented(std::string const&); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates forbidden - ////////////////////////////////////////////////////////////////////////////// - - void generateForbidden(); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates precondition failed, without transaction info - /// DEPRECATED - ////////////////////////////////////////////////////////////////////////////// - - void generatePreconditionFailed(std::string const&, std::string const& key, - TRI_voc_rid_t rev); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates precondition failed, without transaction info - ////////////////////////////////////////////////////////////////////////////// - - void generatePreconditionFailed(arangodb::velocypack::Slice const& slice); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates not modified - ////////////////////////////////////////////////////////////////////////////// - - void generateNotModified(TRI_voc_rid_t); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generates first entry from a result set - ////////////////////////////////////////////////////////////////////////////// - - void generateDocument(arangodb::velocypack::Slice const&, bool, - arangodb::velocypack::Options const* options = nullptr); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generate an error message for a transaction error, this method - /// is used by the others. - ////////////////////////////////////////////////////////////////////////////// - - void generateTransactionError(std::string const&, OperationResult const&, - std::string const& key, TRI_voc_rid_t = 0); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generate an error message for a transaction error - ////////////////////////////////////////////////////////////////////////////// - - void generateTransactionError(std::string const& str, Result const& res, - std::string const& key, TRI_voc_rid_t rid = 0) { - generateTransactionError(str, OperationResult(res), key, rid); - } - - ////////////////////////////////////////////////////////////////////////////// - /// @brief generate an error message for a transaction error - ////////////////////////////////////////////////////////////////////////////// - - void generateTransactionError(OperationResult const& result) { - generateTransactionError("", result, "", 0); - } - - ////////////////////////////////////////////////////////////////////////////// - /// @brief extracts the revision - /// - /// @note @FA{header} must be lowercase. - ////////////////////////////////////////////////////////////////////////////// - - TRI_voc_rid_t extractRevision(char const*, bool&) const; - - //////////////////////////////////////////////////////////////////////////////// - /// @brief extracts a string parameter value - //////////////////////////////////////////////////////////////////////////////// - - void extractStringParameter(std::string const& name, std::string& ret) const; - - /** - * @brief Helper to create a new Transaction for a single collection. The - * helper method will consider no-lock headers send via http and will lock the - * collection accordingly. - * - * @param collectionName Name of the collection to be locked - * @param mode The access mode (READ / WRITE / EXCLUSIVE) - * - * @return A freshly created transaction for the given collection with proper - * locking. - */ - std::unique_ptr createTransaction(std::string const& cname, - AccessMode::Type mode) const; - - /// @brief create proper transaction context, inclusing the proper IDs - std::shared_ptr createAQLTransactionContext() const; - - protected: - ////////////////////////////////////////////////////////////////////////////// - /// @brief request context - ////////////////////////////////////////////////////////////////////////////// - - VocbaseContext& _context; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief the vocbase, managed by VocbaseContext do not release - ////////////////////////////////////////////////////////////////////////////// - - TRI_vocbase_t& _vocbase; - - public: - virtual bool cancel() override { _context.cancel(); return RestBaseHandler::cancel(); } + protected: + /// @brief assemble a document id from a string and a string + /// optionally url-encodes + std::string assembleDocumentId(std::string const& collectionName, + std::string const& key, bool urlEncode); + + /// @brief generates a HTTP 201 or 202 response + void generate20x(arangodb::OperationResult const&, std::string const&, + TRI_col_type_e, arangodb::velocypack::Options const*, + bool isMultiple, rest::ResponseCode waitForSyncResponseCode); + + /// @brief generates message for a saved document + void generateSaved(arangodb::OperationResult const& result, + std::string const& collectionName, TRI_col_type_e type, + arangodb::velocypack::Options const*, bool isMultiple); + + /// @brief generates deleted message + void generateDeleted(arangodb::OperationResult const& result, + std::string const& collectionName, TRI_col_type_e type, + arangodb::velocypack::Options const*, bool isMultiple); + + /// @brief generates document not found error message, no transaction info + void generateDocumentNotFound(std::string const& /* collection name */, + std::string const& /* document key */) { + generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND); + } + + /// @brief generates not implemented + void generateNotImplemented(std::string const& path); + + /// @brief generates forbidden + void generateForbidden(); + + /// @brief generates precondition failed, without transaction info + /// DEPRECATED + void generatePreconditionFailed(std::string const&, std::string const& key, + TRI_voc_rid_t rev); + + /// @brief generates precondition failed, without transaction info + void generatePreconditionFailed(arangodb::velocypack::Slice const& slice); + + /// @brief generates not modified + void generateNotModified(TRI_voc_rid_t); + + /// @brief generates first entry from a result set + void generateDocument(arangodb::velocypack::Slice const& input, + bool generateBody, + arangodb::velocypack::Options const* options = nullptr); + + /// @brief generate an error message for a transaction error, this method + /// is used by the others. + void generateTransactionError(std::string const& collectionName, + OperationResult const& result, + std::string const& key, TRI_voc_rid_t = 0); + + /// @brief generate an error message for a transaction error + void generateTransactionError(std::string const& collectionName, + Result const& res, + std::string const& key, TRI_voc_rid_t rid = 0) { + generateTransactionError(collectionName, OperationResult(res), key, rid); + } + + /// @brief generate an error message for a transaction error + void generateTransactionError(OperationResult const& result) { + generateTransactionError("", result, "", 0); + } + + /// @brief extracts the revision. "header" must be lowercase. + TRI_voc_rid_t extractRevision(char const* header, bool& isValid) const; + + /// @brief extracts a string parameter value + void extractStringParameter(std::string const& name, std::string& ret) const; + + /** + * @brief Helper to create a new Transaction for a single collection. The + * helper method will will lock the collection accordingly. It will additionally + * check if there is a transaction-id header and will make use of an existing + * transaction if a transaction id is specified. it can also start a new + * transaction lazily if requested. + * + * @param collectionName Name of the collection to be locked + * @param mode The access mode (READ / WRITE / EXCLUSIVE) + * + * @return A freshly created transaction for the given collection with proper + * locking or a leased transaction. + */ + std::unique_ptr createTransaction(std::string const& cname, + AccessMode::Type mode) const; + + /// @brief create proper transaction context, including the proper IDs + std::shared_ptr createTransactionContext() const; + + protected: + /// @brief request context + VocbaseContext& _context; + + /// @brief the vocbase, managed by VocbaseContext + TRI_vocbase_t& _vocbase; }; } // namespace arangodb diff --git a/arangod/V8Server/v8-general-graph.cpp b/arangod/V8Server/v8-general-graph.cpp index 9f6b9fe14a..3ee7624458 100644 --- a/arangod/V8Server/v8-general-graph.cpp +++ b/arangod/V8Server/v8-general-graph.cpp @@ -189,7 +189,8 @@ static void JS_GetGraphs(v8::FunctionCallbackInfo const& args) { } if (!result.isEmpty()) { - TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice().get("graphs"))); + auto ctx = std::make_shared(vocbase); + TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice().get("graphs"), ctx->getVPackOptionsForDump())); } TRI_V8_RETURN_UNDEFINED(); @@ -306,7 +307,8 @@ static void JS_AddEdgeDefinitions(v8::FunctionCallbackInfo const& arg } TRI_ASSERT(graph.get() != nullptr); - GraphOperations gops{*graph.get(), vocbase}; + auto ctx = transaction::V8Context::Create(vocbase, true); + GraphOperations gops{*graph.get(), vocbase, ctx}; OperationResult r = gops.addEdgeDefinition(edgeDefinition.slice(), false); if (r.fail()) { @@ -354,7 +356,8 @@ static void JS_EditEdgeDefinitions(v8::FunctionCallbackInfo const& ar } TRI_ASSERT(graph.get() != nullptr); - GraphOperations gops{*(graph.get()), vocbase}; + auto ctx = transaction::V8Context::Create(vocbase, true); + GraphOperations gops{*graph.get(), vocbase, ctx}; OperationResult r = gops.editEdgeDefinition(edgeDefinition.slice(), false, edgeDefinition.slice().get("collection").copyString()); @@ -420,7 +423,8 @@ static void JS_RemoveVertexCollection(v8::FunctionCallbackInfo const& builder.add("collection", VPackValue(vertexName)); builder.close(); - GraphOperations gops{*(graph.get()), vocbase}; + auto ctx = transaction::V8Context::Create(vocbase, true); + GraphOperations gops{*graph.get(), vocbase, ctx}; OperationResult r = gops.eraseOrphanCollection(false, vertexName, dropCollection); if (r.fail()) { @@ -472,7 +476,7 @@ static void JS_AddVertexCollection(v8::FunctionCallbackInfo const& ar } auto& vocbase = GetContextVocBase(isolate); - auto ctx = transaction::V8Context::Create(vocbase, false); + auto ctx = transaction::V8Context::Create(vocbase, true); GraphManager gmngr{vocbase}; auto graph = gmngr.lookupGraphByName(graphName); @@ -481,7 +485,7 @@ static void JS_AddVertexCollection(v8::FunctionCallbackInfo const& ar } TRI_ASSERT(graph.get() != nullptr); - GraphOperations gops{*(graph.get()), vocbase}; + GraphOperations gops{*graph.get(), vocbase, ctx}; VPackBuilder builder; builder.openObject(); @@ -548,7 +552,8 @@ static void JS_DropEdgeDefinition(v8::FunctionCallbackInfo const& arg } TRI_ASSERT(graph.get() != nullptr); - GraphOperations gops{*(graph.get()), vocbase}; + auto ctx = transaction::V8Context::Create(vocbase, true); + GraphOperations gops{*graph.get(), vocbase, ctx}; OperationResult r = gops.eraseEdgeDefinition(false, edgeDefinitionName, dropCollections); if (r.fail()) { diff --git a/tests/js/client/shell/shell-graph-transaction.js b/tests/js/client/shell/shell-graph-transaction.js new file mode 100644 index 0000000000..34b6862656 --- /dev/null +++ b/tests/js/client/shell/shell-graph-transaction.js @@ -0,0 +1,759 @@ +/* jshint globalstrict:false, strict:false, maxlen: 200 */ +/* global fail, arango, assertTrue, assertFalse, assertEqual, assertNotUndefined */ + +// ////////////////////////////////////////////////////////////////////////////// +// / @brief ArangoTransaction tests for graphs +// / +// / +// / 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 triAGENS GmbH, Cologne, Germany +// / +// / @author Jan Steemann +// ////////////////////////////////////////////////////////////////////////////// + +var jsunity = require('jsunity'); +var arangodb = require('@arangodb'); +var ERRORS = arangodb.errors; +var Graph = require('@arangodb/general-graph'); +var db = arangodb.db; + +function JSTransactionGraphSuite () { + 'use strict'; + const graph = 'UnitTestsGraph'; + const vertex = 'UnitTestsVertex'; + const edge = 'UnitTestsEdge'; + let g; + + return { + setUp: function () { + if (Graph._exists(graph)) { + Graph._drop(graph, true); + } + g = Graph._create(graph, + [Graph._relation(edge, vertex, vertex)] + ); + }, + + tearDown: function () { + Graph._drop(graph, true); + }, + + // / @brief test: insert vertex + testVertexInsertUndeclaredJS: function () { + try { + db._executeTransaction({ + collections: {}, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + graph[params.vertex].insert({ _key: "test", value: "test" }); + }, + params: { vertex, graph } + }); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_TRANSACTION_UNREGISTERED_COLLECTION.code, err.errorNum); + } + }, + + // / @brief test: insert edge + testEdgeInsertUndeclaredJS: function () { + try { + db._executeTransaction({ + collections: {}, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + graph[params.edge].insert({ _key: "test", value: "test", _from: params.vertex + "/1", _to: params.vertex + "/2" }); + }, + params: { vertex, edge, graph } + }); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_TRANSACTION_UNREGISTERED_COLLECTION.code, err.errorNum); + } + }, + + // / @brief test: insert vertex + testVertexInsertJS: function () { + db._executeTransaction({ + collections: { write: vertex }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + graph[params.vertex].insert({ _key: "test", value: "test" }); + + let result = graph[params.vertex].document("test"); + if (result.value !== "test") { + throw "peng"; + } + }, + params: { vertex, graph } + }); + + let result = db._collection(vertex).document("test"); + assertEqual("test", result.value); + }, + + // / @brief test: insert edge + testEdgeInsertJS: function () { + db._executeTransaction({ + collections: { write: [ vertex, edge ] }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + graph[params.vertex].insert({ _key: "1", value: "test" }); + graph[params.vertex].insert({ _key: "2", value: "test" }); + graph[params.edge].insert({ _key: "test", value: "test", _from: params.vertex + "/1", _to: params.vertex + "/2" }); + + let result = graph[params.edge].document("test"); + if (result.value !== "test") { + throw "peng"; + } + }, + params: { vertex, edge, graph } + }); + + let result = db._collection(edge).document("test"); + assertEqual("test", result.value); + assertEqual(vertex + "/1", result._from); + assertEqual(vertex + "/2", result._to); + }, + + // / @brief test: remove vertex + testVertexRemoveJS: function () { + db[vertex].insert({ _key: "test", value: "test" }); + + db._executeTransaction({ + collections: { write: vertex }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + let ERRORS = require("@arangodb").errors; + graph[params.vertex].remove("test"); + + try { + graph[params.vertex].document("test"); + throw "peng"; + } catch (err) { + if (ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code !== err.errorNum) { + throw "peng"; + } + } + }, + params: { vertex, graph } + }); + + try { + db._collection(vertex).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + }, + + // / @brief test: remove vertex with dependencies + testVertexRemoveWithDependenciesJS: function () { + db[vertex].insert({ _key: "1" }); + db[vertex].insert({ _key: "2" }); + db[edge].insert({ _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + db._executeTransaction({ + collections: { write: [ vertex, edge ] }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + let ERRORS = require("@arangodb").errors; + graph[params.vertex].remove("1"); + + try { + graph[params.vertex].document("1"); + throw "peng"; + } catch (err) { + if (ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code !== err.errorNum) { + throw "peng"; + } + } + + try { + graph[params.edge].document("test"); + throw "peng"; + } catch (err) { + if (ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code !== err.errorNum) { + throw "peng"; + } + } + }, + params: { vertex, edge, graph } + }); + + try { + db._collection(vertex).document("1"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + + try { + db._collection(edge).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + }, + + // / @brief test: remove vertex with dependencies + testVertexRemoveWithDependenciesUndeclaredVertexCollectionJS: function () { + db[vertex].insert({ _key: "1" }); + db[vertex].insert({ _key: "2" }); + db[edge].insert({ _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + try { + db._executeTransaction({ + collections: { write: edge }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + graph[params.vertex].remove("1"); + throw "peng"; + }, + params: { vertex, edge, graph } + }); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_TRANSACTION_UNREGISTERED_COLLECTION.code, err.errorNum); + } + + let result = db._collection(vertex).document("1"); + assertEqual("1", result._key); + result = db._collection(edge).document("test"); + assertEqual("test", result.value); + }, + + // / @brief test: remove edge + testEdgeRemoveJS: function () { + db[vertex].insert({ _key: "1" }); + db[vertex].insert({ _key: "2" }); + db[edge].insert({ _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + db._executeTransaction({ + collections: { write: edge }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + let ERRORS = require("@arangodb").errors; + graph[params.edge].remove("test"); + + try { + graph[params.edge].document("test"); + throw "peng"; + } catch (err) { + if (ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code !== err.errorNum) { + throw "peng"; + } + } + }, + params: { edge, graph } + }); + + try { + db._collection(edge).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + }, + + // / @brief test: update edge + testEdgeUpdateJS: function () { + db[vertex].insert({ _key: "1" }); + db[vertex].insert({ _key: "2" }); + db[edge].insert({ _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + db._executeTransaction({ + collections: { write: edge }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + graph[params.edge].update("test", { value: "meow" }); + + let result = graph[params.edge].document("test"); + if (result.value !== "meow") { + throw "peng"; + } + }, + params: { edge, graph } + }); + + let result = db._collection(edge).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + }, + + // / @brief test: update vertex + testVertexUpdateJS: function () { + db[vertex].insert({ _key: "test", value: "test" }); + + db._executeTransaction({ + collections: { write: vertex }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + graph[params.vertex].update("test", { value: "meow" }); + + let result = graph[params.vertex].document("test"); + if (result.value !== "meow") { + throw "peng"; + } + }, + params: { vertex, graph } + }); + + let result = db._collection(vertex).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + }, + + // / @brief test: replace edge + testEdgeReplaceJS: function () { + db[vertex].insert({ _key: "1" }); + db[vertex].insert({ _key: "2" }); + db[edge].insert({ _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + db._executeTransaction({ + collections: { write: edge }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + graph[params.edge].replace("test", { value: "meow", _from: params.vertex + "/1", _to: params.vertex + "/2" }); + + let result = graph[params.edge].document("test"); + if (result.value !== "meow") { + throw "peng"; + } + }, + params: { vertex, edge, graph } + }); + + let result = db._collection(edge).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + }, + + // / @brief test: replace vertex + testVertexReplaceJS: function () { + db[vertex].insert({ _key: "test", value: "test" }); + + db._executeTransaction({ + collections: { write: vertex }, + action: function(params) { + let graph = require("@arangodb/general-graph")._graph(params.graph); + graph[params.vertex].replace("test", { value: "meow" }); + + let result = graph[params.vertex].document("test"); + if (result.value !== "meow") { + throw "peng"; + } + }, + params: { vertex, graph } + }); + + let result = db._collection(vertex).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + }, + }; +} + +function TransactionGraphSuite () { + 'use strict'; + const graph = 'UnitTestsGraph'; + const vertex = 'UnitTestsVertex'; + const edge = 'UnitTestsEdge'; + let g; + + return { + setUp: function () { + if (Graph._exists(graph)) { + Graph._drop(graph, true); + } + g = Graph._create(graph, + [Graph._relation(edge, vertex, vertex)] + ); + }, + + tearDown: function () { + db._transactions().forEach(function(trx) { + try { + db._createTransaction(trx.id).abort(); + } catch (err) {} + }); + Graph._drop(graph, true); + }, + + // / @brief test: insert vertex + testVertexInsertUndeclared: function () { + let trx = db._createTransaction({ + collections: {} + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "test", value: "test" }, headers); + assertTrue(result.error); + assertEqual(ERRORS.ERROR_TRANSACTION_UNREGISTERED_COLLECTION.code, result.errorNum); + }, + + // / @brief test: insert edge + testEdgeInsertUndeclared: function () { + let trx = db._createTransaction({ + collections: {} + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.POST("/_api/gharial/" + graph + "/edge/" + edge, { _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }, headers); + assertTrue(result.error); + assertEqual(ERRORS.ERROR_TRANSACTION_UNREGISTERED_COLLECTION.code, result.errorNum); + }, + + // / @brief test: insert vertex + testVertexInsert: function () { + let trx = db._createTransaction({ + collections: { write: [ vertex ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "test", value: "test" }, headers); + assertFalse(result.error); + assertEqual(202, result.code); + + if (db._engine().name !== 'mmfiles') { + try { + db._collection(vertex).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + } + + trx.commit(); + + result = db._collection(vertex).document("test"); + assertEqual("test", result.value); + }, + + // / @brief test: insert edge + testEdgeInsert: function () { + let trx = db._createTransaction({ + collections: { write: [ vertex, edge ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "1" }, headers); + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "2" }, headers); + let result = arango.POST("/_api/gharial/" + graph + "/edge/" + edge, { _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }, headers); + assertFalse(result.error); + assertEqual(202, result.code); + + if (db._engine().name !== 'mmfiles') { + try { + db._collection(edge).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + } + + trx.commit(); + + result = db._collection(edge).document("test"); + assertEqual("test", result.value); + assertEqual(vertex + "/1", result._from); + assertEqual(vertex + "/2", result._to); + }, + + // / @brief test: remove vertex + testVertexRemove: function () { + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "test", value: "test" }); + + let trx = db._createTransaction({ + collections: { write: [ vertex, edge ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.DELETE("/_api/gharial/" + graph + "/vertex/" + vertex + "/test", null, headers); + assertFalse(result.error); + assertEqual(202, result.code); + + if (db._engine().name !== 'mmfiles') { + result = db._collection(vertex).document("test"); + assertEqual("test", result._key); + assertEqual("test", result.value); + } + + try { + trx.collection(vertex).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + + trx.commit(); + + try { + db._collection(vertex).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + }, + + // / @brief test: remove vertex with dependencies + testVertexRemoveWithDependencies: function () { + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "1" }); + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "2" }); + arango.POST("/_api/gharial/" + graph + "/edge/" + edge, { _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + let trx = db._createTransaction({ + collections: { write: [ vertex, edge ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.DELETE("/_api/gharial/" + graph + "/vertex/" + vertex + "/1", null, headers); + assertFalse(result.error); + assertEqual(202, result.code); + + if (db._engine().name !== 'mmfiles') { + result = db._collection(vertex).document("1"); + assertEqual("1", result._key); + result = db._collection(edge).document("test"); + assertEqual("test", result.value); + } + + if (db._engine().name !== 'mmfiles') { + try { + trx.collection(vertex).document("1"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + } + + try { + trx.collection(edge).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + + trx.commit(); + + try { + db._collection(vertex).document("1"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + + try { + db._collection(edge).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + }, + + // / @brief test: remove vertex with dependencies + testVertexRemoveWithDependenciesUndeclaredVertexCollection: function () { + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "1" }); + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "2" }); + arango.POST("/_api/gharial/" + graph + "/edge/" + edge, { _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + let trx = db._createTransaction({ + collections: { write: [ edge ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.DELETE("/_api/gharial/" + graph + "/vertex/" + vertex + "/1", null, headers); + assertTrue(result.error); + assertEqual(ERRORS.ERROR_TRANSACTION_UNREGISTERED_COLLECTION.code, result.errorNum); + + trx.abort(); + + result = db._collection(vertex).document("1"); + assertEqual("1", result._key); + result = db._collection(edge).document("test"); + assertEqual("test", result.value); + }, + + // / @brief test: remove edge + testEdgeRemove: function () { + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "1" }); + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "2" }); + arango.POST("/_api/gharial/" + graph + "/edge/" + edge, { _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + let trx = db._createTransaction({ + collections: { write: [ edge ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.DELETE("/_api/gharial/" + graph + "/edge/" + edge + "/test", null, headers); + assertFalse(result.error); + assertEqual(202, result.code); + + if (db._engine().name !== 'mmfiles') { + result = db._collection(edge).document("test"); + assertEqual("test", result._key); + assertEqual("test", result.value); + } + + try { + trx.collection(edge).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + + trx.commit(); + + try { + db._collection(edge).document("test"); + fail(); + } catch (err) { + assertEqual(ERRORS.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, err.errorNum); + } + }, + + // / @brief test: update edge + testEdgeUpdate: function () { + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "1" }); + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "2" }); + arango.POST("/_api/gharial/" + graph + "/edge/" + edge, { _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + let trx = db._createTransaction({ + collections: { write: [ edge ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.PATCH("/_api/gharial/" + graph + "/edge/" + edge + "/test", { value: "meow" }, headers); + assertFalse(result.error); + assertEqual(202, result.code); + + if (db._engine().name !== 'mmfiles') { + result = db._collection(edge).document("test"); + assertEqual("test", result._key); + assertEqual("test", result.value); + + result = trx.collection(edge).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + } + + trx.commit(); + + result = db._collection(edge).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + }, + + // / @brief test: update vertex + testVertexUpdate: function () { + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "test", value: "test" }); + + let trx = db._createTransaction({ + collections: { write: [ vertex ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.PATCH("/_api/gharial/" + graph + "/vertex/" + vertex + "/test", { value: "meow" }, headers); + assertFalse(result.error); + assertEqual(202, result.code); + + if (db._engine().name !== 'mmfiles') { + result = db._collection(vertex).document("test"); + assertEqual("test", result._key); + assertEqual("test", result.value); + + result = trx.collection(vertex).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + } + + trx.commit(); + + result = db._collection(vertex).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + }, + + // / @brief test: replace edge + testEdgeReplace: function () { + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "1" }); + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "2" }); + arango.POST("/_api/gharial/" + graph + "/edge/" + edge, { _key: "test", value: "test", _from: vertex + "/1", _to: vertex + "/2" }); + + let trx = db._createTransaction({ + collections: { write: [ edge ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.PUT("/_api/gharial/" + graph + "/edge/" + edge + "/test", { value: "meow", _from: vertex + "/1", _to: vertex + "/2" }, headers); + assertFalse(result.error); + assertEqual(202, result.code); + + if (db._engine().name !== 'mmfiles') { + result = db._collection(edge).document("test"); + assertEqual("test", result._key); + assertEqual("test", result.value); + + result = trx.collection(edge).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + } + + trx.commit(); + + result = db._collection(edge).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + }, + + // / @brief test: replace vertex + testVertexReplace: function () { + arango.POST("/_api/gharial/" + graph + "/vertex/" + vertex, { _key: "test", value: "test" }); + + let trx = db._createTransaction({ + collections: { write: [ vertex ] } + }); + + let headers = { "x-arango-trx-id" : trx.id() }; + let result = arango.PUT("/_api/gharial/" + graph + "/vertex/" + vertex + "/test", { value: "meow" }, headers); + assertFalse(result.error); + assertEqual(202, result.code); + + if (db._engine().name !== 'mmfiles') { + result = db._collection(vertex).document("test"); + assertEqual("test", result._key); + assertEqual("test", result.value); + + result = trx.collection(vertex).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + } + + trx.commit(); + + result = db._collection(vertex).document("test"); + assertEqual("test", result._key); + assertEqual("meow", result.value); + }, + }; +} + +jsunity.run(JSTransactionGraphSuite); +jsunity.run(TransactionGraphSuite); + +return jsunity.done();