1
0
Fork 0

make graphs transaction-aware (#9855)

* make graphs transaction-aware

* simranification

* fix tests with mmfiles
This commit is contained in:
Jan 2019-09-02 21:06:10 +02:00 committed by KVS85
parent 10d99b20cb
commit f8c156a44f
10 changed files with 927 additions and 301 deletions

View File

@ -1,6 +1,10 @@
v3.5.1 (XXXX-XX-XX)
-------------------
* Make graph operations in general-graph transaction-aware.
* Fixed adding an orphan collections as the first collection in a SmartGraph.
* Fixed non-deterministic occurrences of "document not found" errors in sharded
collections with custom shard keys (i.e. non-`_key`) and multi-document lookups.
@ -10,8 +14,6 @@ v3.5.1 (XXXX-XX-XX)
internal iterator after being rearmed with new lookup values (which only happens
if the IN iterator is called from an inner FOR loop).
* Fixed adding an orphan collection as the first collection in a SmartGraph.
* Geo functions will now have better error reporting on invalid input.
* The graph viewer of the web interface now tries to find a vertex document of

View File

@ -235,8 +235,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();

View File

@ -51,9 +51,12 @@
using namespace arangodb;
using namespace arangodb::graph;
std::shared_ptr<transaction::Context> GraphOperations::ctx() const {
return transaction::StandaloneContext::Create(_vocbase);
};
std::shared_ptr<transaction::Context> GraphOperations::ctx() {
if (!_ctx) {
_ctx = std::make_shared<transaction::StandaloneSmartContext>(_vocbase);
}
return _ctx;
}
void GraphOperations::checkForUsedEdgeCollections(const Graph& graph,
const std::string& collectionName,
@ -456,7 +459,7 @@ OperationResult GraphOperations::addEdgeDefinition(VPackSlice edgeDefinitionSlic
// finally save the graph
return gmngr.storeGraph(_graph, waitForSync, true);
};
}
// vertices
@ -466,7 +469,7 @@ OperationResult GraphOperations::getVertex(std::string const& collectionName,
std::string const& key,
boost::optional<TRI_voc_rid_t> rev) {
return getDocument(collectionName, key, std::move(rev));
};
}
// TODO check if definitionName is an edge collection in _graph?
OperationResult GraphOperations::getEdge(const std::string& definitionName,
@ -867,10 +870,9 @@ OperationResult GraphOperations::removeEdgeOrVertex(const std::string& collectio
edgeCollections.emplace(it); // but also to edgeCollections for later iteration
}
auto ctx = std::make_shared<transaction::StandaloneSmartContext>(_vocbase);
transaction::Options trxOptions;
trxOptions.waitForSync = waitForSync;
transaction::Methods trx{ctx, {}, trxCollections, {}, trxOptions};
transaction::Methods trx{ctx(), {}, trxCollections, {}, trxOptions};
res = trx.begin();
@ -898,7 +900,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());

View File

@ -48,14 +48,16 @@ class GraphOperations {
private:
Graph& _graph;
TRI_vocbase_t& _vocbase;
std::shared_ptr<transaction::Context> _ctx;
Graph const& graph() const { return _graph; };
std::shared_ptr<transaction::Context> ctx() const;
std::shared_ptr<transaction::Context> 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<transaction::Context> 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

View File

@ -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<aql::Query>(
false, _vocbase, arangodb::aql::QueryString(queryStr, static_cast<size_t>(l)),
bindVarsBuilder, _options, arangodb::aql::PART_MAIN);
query->setTransactionContext(createAQLTransactionContext());
query->setTransactionContext(createTransactionContext());
std::shared_ptr<aql::SharedQueryState> ss = query->sharedState();
ss->setContinueHandler([self = shared_from_this(), ss] { self->continueHandlerExecution(); });

View File

@ -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<transaction::StandaloneContext>(_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<transaction::StandaloneContext>(_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<transaction::StandaloneContext>(_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<transaction::StandaloneContext>(_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<transaction::StandaloneContext>(_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<VPackBuilder> 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<transaction::StandaloneContext>(_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,10 +851,7 @@ 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<transaction::StandaloneContext>(_vocbase);
} else {
switch (colType) {
case TRI_COL_TYPE_DOCUMENT:
generateVertexCreated(result._options.waitForSync, result.slice(),
@ -869,8 +864,9 @@ Result RestGraphHandler::documentCreate(graph::Graph& graph, const std::string&
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<transaction::StandaloneContext>(_vocbase);
generateRemoved(true, result._options.waitForSync, result.slice().get("old"),
*ctx->getVPackOptionsForDump());

View File

@ -544,7 +544,7 @@ void RestVocbaseBaseHandler::extractStringParameter(std::string const& name,
}
std::unique_ptr<SingleCollectionTransaction> 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) {
@ -581,15 +581,15 @@ std::unique_ptr<SingleCollectionTransaction> 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<SingleCollectionTransaction>(ctx, name, type);
return std::make_unique<SingleCollectionTransaction>(ctx, collectionName, type);
} else {
auto ctx = transaction::StandaloneContext::Create(_vocbase);
return std::make_unique<SingleCollectionTransaction>(ctx, name, type);
return std::make_unique<SingleCollectionTransaction>(ctx, collectionName, type);
}
}
/// @brief create proper transaction context, inclusing the proper IDs
std::shared_ptr<transaction::Context> RestVocbaseBaseHandler::createAQLTransactionContext() const {
std::shared_ptr<transaction::Context> RestVocbaseBaseHandler::createTransactionContext() const {
bool found = false;
std::string value = _request->header(StaticStrings::TransactionId, found);
if (!found) {

View File

@ -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<SingleCollectionTransaction> createTransaction(std::string const& cname,
AccessMode::Type mode) const;
/// @brief create proper transaction context, inclusing the proper IDs
std::shared_ptr<transaction::Context> 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<SingleCollectionTransaction> createTransaction(std::string const& cname,
AccessMode::Type mode) const;
/// @brief create proper transaction context, including the proper IDs
std::shared_ptr<transaction::Context> createTransactionContext() const;
protected:
/// @brief request context
VocbaseContext& _context;
/// @brief the vocbase, managed by VocbaseContext
TRI_vocbase_t& _vocbase;
};
} // namespace arangodb

View File

@ -189,7 +189,8 @@ static void JS_GetGraphs(v8::FunctionCallbackInfo<v8::Value> const& args) {
}
if (!result.isEmpty()) {
TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice().get("graphs")));
auto ctx = std::make_shared<transaction::StandaloneContext>(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<v8::Value> 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<v8::Value> 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<v8::Value> 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<v8::Value> 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<v8::Value> 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<v8::Value> 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()) {

View File

@ -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();