mirror of https://gitee.com/bigwinds/arangodb
make graphs transaction-aware (#9855)
* make graphs transaction-aware * simranification * fix tests with mmfiles
This commit is contained in:
parent
10d99b20cb
commit
f8c156a44f
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(); });
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
Loading…
Reference in New Issue