diff --git a/Documentation/Books/Manual/Graphs/GeneralGraphs/Management.md b/Documentation/Books/Manual/Graphs/GeneralGraphs/Management.md index 82127f16c6..7db6a0e696 100644 --- a/Documentation/Books/Manual/Graphs/GeneralGraphs/Management.md +++ b/Documentation/Books/Manual/Graphs/GeneralGraphs/Management.md @@ -384,7 +384,7 @@ graph with different *from* and/or *to* collections an error is thrown. Modify an relation definition -`graph_module._editEdgeDefinition(edgeDefinition)` +`graph_module._editEdgeDefinitions(edgeDefinition)` Edits one relation definition of a graph. The edge definition used as argument will replace the existing edge definition of the graph which has the same collection. diff --git a/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_delete_http_examples.md b/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_delete_http_examples.md index 4464b956c3..98147bd5f5 100644 --- a/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_delete_http_examples.md +++ b/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_delete_http_examples.md @@ -23,6 +23,10 @@ The *_key* attribute of the vertex. @RESTQUERYPARAM{waitForSync,boolean,optional} Define if the request should wait until synced to disk. +@RESTQUERYPARAM{returnOld,boolean,optional} +Define if a presentation of the deleted document should +be returned within the response object. + @RESTHEADERPARAMETERS @RESTHEADERPARAM{if-match,string,optional} diff --git a/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_modify_http_examples.md b/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_modify_http_examples.md index fb110d86a8..39342c6312 100644 --- a/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_modify_http_examples.md +++ b/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_modify_http_examples.md @@ -26,6 +26,16 @@ Define if the request should wait until synced to disk. @RESTQUERYPARAM{keepNull,boolean,optional} Define if values set to null should be stored. By default the key is not removed from the document. +@RESTQUERYPARAM{returnOld,boolean,optional} +Define if a presentation of the deleted document should +be returned within the response object. + +@RESTQUERYPARAM{returnNew,boolean,optional} +Define if a presentation of the newly create + +@RESTQUERYPARAM{keepNull,boolean,optional} +Define if values set to null should be stored. By default the key is not removed from the document. + @RESTALLBODYPARAM{updateAttributes,object,required} The body has to be a JSON object containing the attributes to be updated. diff --git a/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_replace_http_examples.md b/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_replace_http_examples.md index 3758a31cba..1e66ae8c14 100644 --- a/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_replace_http_examples.md +++ b/Documentation/DocuBlocks/Rest/Graph/general_graph_edge_replace_http_examples.md @@ -22,6 +22,16 @@ The *_key* attribute of the vertex. @RESTQUERYPARAM{waitForSync,boolean,optional} Define if the request should wait until synced to disk. +@RESTQUERYPARAM{keepNull,boolean,optional} +Define if values set to null should be stored. By default the key is not removed from the document. + +@RESTQUERYPARAM{returnOld,boolean,optional} +Define if a presentation of the deleted document should +be returned within the response object. + +@RESTQUERYPARAM{returnNew,boolean,optional} +Define if a presentation of the newly create + @RESTHEADERPARAMETERS @RESTHEADERPARAM{if-match,string,optional} diff --git a/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_delete_http_examples.md b/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_delete_http_examples.md index e8c40141d8..bf046d0723 100644 --- a/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_delete_http_examples.md +++ b/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_delete_http_examples.md @@ -23,6 +23,14 @@ The *_key* attribute of the vertex. @RESTQUERYPARAM{waitForSync,boolean,optional} Define if the request should wait until synced to disk. +@RESTQUERYPARAM{returnOld,boolean,optional} +Define if a presentation of the deleted document should +be returned within the response object. + +@RESTQUERYPARAM{returnNew,boolean,optional} +Define if a presentation of the newly created document +should be returned within the response object. + @RESTHEADERPARAMETERS @RESTHEADERPARAM{if-match,string,optional} diff --git a/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_modify_http_examples.md b/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_modify_http_examples.md index c99983c63f..e115c6567f 100644 --- a/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_modify_http_examples.md +++ b/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_modify_http_examples.md @@ -26,6 +26,13 @@ Define if the request should wait until synced to disk. @RESTQUERYPARAM{keepNull,boolean,optional} Define if values set to null should be stored. By default the key is not removed from the document. +@RESTQUERYPARAM{returnOld,boolean,optional} +Define if a presentation of the deleted document should +be returned within the response object. + +@RESTQUERYPARAM{returnNew,boolean,optional} +Define if a presentation of the newly create + @RESTHEADERPARAMETERS @RESTHEADERPARAM{if-match,string,optional} diff --git a/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_replace_http_examples.md b/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_replace_http_examples.md index c6136e028c..165a5dfa0f 100644 --- a/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_replace_http_examples.md +++ b/Documentation/DocuBlocks/Rest/Graph/general_graph_vertex_replace_http_examples.md @@ -23,6 +23,16 @@ The *_key* attribute of the vertex. @RESTQUERYPARAM{waitForSync,boolean,optional} Define if the request should wait until synced to disk. +@RESTQUERYPARAM{keepNull,boolean,optional} +Define if values set to null should be stored. By default the key is not removed from the document. + +@RESTQUERYPARAM{returnOld,boolean,optional} +Define if a presentation of the deleted document should +be returned within the response object. + +@RESTQUERYPARAM{returnNew,boolean,optional} +Define if a presentation of the newly create + @RESTHEADERPARAMETERS @RESTHEADERPARAM{if-match,string,optional} diff --git a/UnitTests/HttpInterface/api-general-graph-spec.rb b/UnitTests/HttpInterface/api-general-graph-spec.rb index 02f9437e98..24010d35f1 100644 --- a/UnitTests/HttpInterface/api-general-graph-spec.rb +++ b/UnitTests/HttpInterface/api-general-graph-spec.rb @@ -159,7 +159,6 @@ describe ArangoDB do return doc end - def create_edge (waitForSync, graph_name, collection, from, to, body, options = {}) cmd = edge_endpoint(graph_name, collection) cmd = cmd + "?waitForSync=#{waitForSync}" @@ -282,6 +281,7 @@ describe ArangoDB do second_def = { "collection" => bought_collection, "from" => [user_collection], "to" => [product_collection] } doc = additional_edge_definition(sync, graph_name, second_def ) edge_definition.push(second_def) + edge_definition = edge_definition.sort_by { |d| [ -d["collection"] ] } doc.code.should eq(202) doc.parsed_response['error'].should eq(false) @@ -848,7 +848,10 @@ describe ArangoDB do it "can not replace a non existing edge" do key = "unknownKey" - doc = replace_edge( sync, graph_name, friend_collection, key, {"type2" => "divorced"}) + # Added _from and _to, because otherwise a 400 might conceal the + # 404. Another test checking that missing _from or _to trigger + # errors was added to api-gharial-spec.js. + doc = replace_edge( sync, graph_name, friend_collection, key, {"type2" => "divorced", "_from" => "1", "_to" => "2"}) doc.code.should eq(404) doc.parsed_response['error'].should eq(true) doc.parsed_response['errorMessage'].should include("document not found") @@ -1169,11 +1172,18 @@ describe ArangoDB do doc.parsed_response['code'].should eq(404) end + def check400 (doc) + doc.code.should eq(400) + doc.parsed_response['error'].should eq(true) + doc.parsed_response['code'].should eq(400) + puts doc.parsed_response['errorMessage'] + doc.parsed_response['errorMessage'].should include("edge attribute missing or invalid") + end + def check404Edge (doc) check404(doc) doc.parsed_response['errorNum'].should eq(1930) doc.parsed_response['errorMessage'].should eq("edge collection not used in graph") - end def check404Vertex (doc) @@ -1181,10 +1191,23 @@ describe ArangoDB do doc.parsed_response['errorNum'].should eq(1926) end + def check400VertexUnused (doc) + doc.parsed_response['errorNum'].should eq(1928) + doc.parsed_response['error'].should eq(true) + doc.parsed_response['code'].should eq(400) + puts doc.parsed_response['errorMessage'] + doc.parsed_response['errorMessage'].should include("not in orphan collection") + end + def check404CRUD (doc) check404(doc) doc.parsed_response['errorNum'].should eq(1203) - doc.parsed_response['errorMessage'].should eq("collection or view not found") + doc.parsed_response['errorMessage'].should start_with("collection or view not found: ") + end + + def check400CRUD (doc) + check400(doc) + doc.parsed_response['errorNum'].should eq(1233) end it "change edge definition" do @@ -1197,7 +1220,8 @@ describe ArangoDB do end it "delete vertex collection" do - check404Vertex(delete_vertex_collection( sync, graph_name, unknown_name)) + # this checks if a not used vertex collection can be removed of a graph + check400VertexUnused(delete_vertex_collection( sync, graph_name, unknown_name)) end it "create vertex" do @@ -1208,6 +1232,8 @@ describe ArangoDB do check404CRUD(get_vertex(graph_name, unknown_name, unknown_name)) end +# TODO add tests where the edge/vertex collection is not part of the graph, but +# the given key exists! it "update vertex" do check404CRUD(update_vertex( sync, graph_name, unknown_name, unknown_name, {})) end @@ -1221,7 +1247,7 @@ describe ArangoDB do end it "create edge" do - check404CRUD(create_edge( sync, graph_name, unknown_name, unknown_name, unknown_name, {})) + check400CRUD(create_edge( sync, graph_name, unknown_name, unknown_name, unknown_name, {})) end it "get edge" do @@ -1277,7 +1303,10 @@ describe ArangoDB do end it "replace edge" do - check404(replace_edge( sync, graph_name, friend_collection, unknown_name, {})) + # Added _from and _to, because otherwise a 400 might conceal the + # 404. Another test checking that missing _from or _to trigger + # errors was added to api-gharial-spec.js. + check404(replace_edge( sync, graph_name, friend_collection, unknown_name, {"_from" => "1", "_to" => "2"})) end it "delete edge" do diff --git a/arangod/Aql/Ast.cpp b/arangod/Aql/Ast.cpp index a55f73c480..a0d5eeeebf 100644 --- a/arangod/Aql/Ast.cpp +++ b/arangod/Aql/Ast.cpp @@ -35,6 +35,7 @@ #include "Basics/StringUtils.h" #include "Basics/tri-strings.h" #include "Cluster/ClusterInfo.h" +#include "Graph/Graph.h" #include "Transaction/Helpers.h" #include "Utils/CollectionNameResolver.h" #include "VocBase/LogicalCollection.h" diff --git a/arangod/Aql/GraphNode.cpp b/arangod/Aql/GraphNode.cpp index c426c8e194..f91883b14f 100644 --- a/arangod/Aql/GraphNode.cpp +++ b/arangod/Aql/GraphNode.cpp @@ -32,6 +32,7 @@ #include "Aql/Query.h" #include "Cluster/ServerState.h" #include "Graph/BaseOptions.h" +#include "Graph/Graph.h" #include "Utils/CollectionNameResolver.h" #include "VocBase/LogicalCollection.h" diff --git a/arangod/Aql/GraphNode.h b/arangod/Aql/GraphNode.h index 6ac16ee715..35290776aa 100644 --- a/arangod/Aql/GraphNode.h +++ b/arangod/Aql/GraphNode.h @@ -32,12 +32,11 @@ namespace arangodb { namespace graph { struct BaseOptions; +class Graph; } namespace aql { -class Graph; - // @brief This is a pure virtual super-class for all AQL graph operations // It does the generally required: // * graph info parsing @@ -145,7 +144,7 @@ class GraphNode : public ExecutionNode { Variable const* _edgeOutVariable; /// @brief our graph... - Graph const* _graphObj; + graph::Graph const* _graphObj; /// @brief Temporary pseudo variable for the currently traversed object. Variable const* _tmpObjVariable; diff --git a/arangod/Aql/Graphs.cpp b/arangod/Aql/Graphs.cpp index 691ade10a8..bf85e7e471 100644 --- a/arangod/Aql/Graphs.cpp +++ b/arangod/Aql/Graphs.cpp @@ -21,20 +21,17 @@ /// @author Michael Hackstein //////////////////////////////////////////////////////////////////////////////// +#include + #include "Graphs.h" #include "Aql/AstNode.h" #include "Basics/StaticStrings.h" #include "Basics/VelocyPackHelper.h" - -#include -#include +#include "Graph/Graph.h" using namespace arangodb::basics; using namespace arangodb::aql; -char const* Graph::_attrEdgeDefs = "edgeDefinitions"; -char const* Graph::_attrOrphans = "orphanCollections"; - EdgeConditionBuilder::EdgeConditionBuilder(AstNode* modCondition) : _fromCondition(nullptr), _toCondition(nullptr), @@ -161,83 +158,3 @@ void EdgeConditionBuilderContainer::setVertexId(std::string const& id) { _compareNode->setStringValue(id.c_str(), id.length()); } -void Graph::insertVertexCollections(VPackSlice& arr) { - TRI_ASSERT(arr.isArray()); - for (auto const& c : VPackArrayIterator(arr)) { - TRI_ASSERT(c.isString()); - addVertexCollection(c.copyString()); - } -} - -std::unordered_set const& Graph::vertexCollections() const { - return _vertexColls; -} - -std::unordered_set const& Graph::edgeCollections() const { - return _edgeColls; -} - -void Graph::addEdgeCollection(std::string const& name) { - _edgeColls.insert(name); -} - -void Graph::addVertexCollection(std::string const& name) { - _vertexColls.insert(name); -} - -void Graph::toVelocyPack(VPackBuilder& builder) const { - VPackObjectBuilder guard(&builder); - - if (!_vertexColls.empty()) { - builder.add(VPackValue("vertexCollectionNames")); - VPackArrayBuilder guard2(&builder); - for (auto const& cn : _vertexColls) { - builder.add(VPackValue(cn)); - } - } - - if (!_edgeColls.empty()) { - builder.add(VPackValue("edgeCollectionNames")); - VPackArrayBuilder guard2(&builder); - for (auto const& cn : _edgeColls) { - builder.add(VPackValue(cn)); - } - } -} - -Graph::Graph(VPackSlice const& slice) : _vertexColls(), _edgeColls() { - if (slice.hasKey(_attrEdgeDefs)) { - auto edgeDefs = slice.get(_attrEdgeDefs); - - for (auto const& def : VPackArrayIterator(edgeDefs)) { - TRI_ASSERT(def.isObject()); - try { - std::string eCol = arangodb::basics::VelocyPackHelper::getStringValue( - def, "collection", ""); - addEdgeCollection(eCol); - } catch (...) { - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_GRAPH_INVALID_GRAPH, "didn't find 'collection' in the graph definition"); - } - // TODO what if graph is not in a valid format any more - try { - VPackSlice tmp = def.get("from"); - insertVertexCollections(tmp); - } catch (...) { - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_GRAPH_INVALID_GRAPH, "didn't find from-collection in the graph definition"); - } - try { - VPackSlice tmp = def.get("to"); - insertVertexCollections(tmp); - } catch (...) { - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_GRAPH_INVALID_GRAPH, "didn't find to-collection in the graph definition"); - } - } - } - if (slice.hasKey(_attrOrphans)) { - auto orphans = slice.get(_attrOrphans); - insertVertexCollections(orphans); - } -} - -void Graph::enhanceEngineInfo(VPackBuilder&) const { -} diff --git a/arangod/Aql/Graphs.h b/arangod/Aql/Graphs.h index 59f9fa3ca1..30214d9d73 100644 --- a/arangod/Aql/Graphs.h +++ b/arangod/Aql/Graphs.h @@ -24,8 +24,8 @@ #ifndef ARANGOD_AQL_GRAPHS_H #define ARANGOD_AQL_GRAPHS_H 1 -#include "Basics/Common.h" #include "Aql/VariableGenerator.h" +#include "Basics/Common.h" namespace arangodb { @@ -141,51 +141,6 @@ class EdgeConditionBuilderContainer final : public EdgeConditionBuilder { VariableGenerator _varGen; }; -class Graph { - public: - explicit Graph(arangodb::velocypack::Slice const&); - - virtual ~Graph() {} - - private: - /// @brief the cids of all vertexCollections - std::unordered_set _vertexColls; - - /// @brief the cids of all edgeCollections - std::unordered_set _edgeColls; - - /// @brief Graph collection edge definition attribute name - static char const* _attrEdgeDefs; - - /// @brief Graph collection orphan list arribute name - static char const* _attrOrphans; - - public: - /// @brief Graph collection name - static std::string const _graphs; - - /// @brief Add Collections to the object - void insertVertexCollections(arangodb::velocypack::Slice& arr); - - public: - /// @brief get the cids of all vertexCollections - std::unordered_set const& vertexCollections() const; - - /// @brief get the cids of all edgeCollections - std::unordered_set const& edgeCollections() const; - - /// @brief Add an edge collection to this graphs definition - void addEdgeCollection(std::string const&); - - /// @brief Add a vertex collection to this graphs definition - void addVertexCollection(std::string const&); - - /// @brief return a VelocyPack representation of the graph - void toVelocyPack(arangodb::velocypack::Builder&) const; - - virtual void enhanceEngineInfo(arangodb::velocypack::Builder&) const; -}; - } // namespace aql } // namespace arangodb diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index 5deff5f514..bd5ae9af17 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -39,6 +39,8 @@ #include "Basics/fasthash.h" #include "Cluster/ServerState.h" #include "GeneralServer/AuthenticationFeature.h" +#include "Graph/Graph.h" +#include "Graph/GraphManager.h" #include "Logger/Logger.h" #include "RestServer/AqlFeature.h" #include "StorageEngine/TransactionState.h" @@ -49,12 +51,9 @@ #include "V8/v8-conv.h" #include "V8/v8-vpack.h" #include "V8Server/V8DealerFeature.h" -#include "VocBase/Graphs.h" #include "VocBase/vocbase.h" -#include #include -#include #ifndef USE_PLAN_CACHE #undef USE_PLAN_CACHE @@ -1444,24 +1443,24 @@ std::shared_ptr Query::createTransactionContext() { /// @brief look up a graph either from our cache list or from the _graphs /// collection -Graph const* Query::lookupGraphByName(std::string const& name) { +graph::Graph const* Query::lookupGraphByName(std::string const& name) { auto it = _graphs.find(name); - if (it == _graphs.end()) { - std::unique_ptr g( - arangodb::lookupGraphByName(createTransactionContext(), name)); - - if (g == nullptr) { - return nullptr; - } - - auto result = _graphs.emplace(name, std::move(g)); - TRI_ASSERT(result.second); - it = result.first; + if (it != _graphs.end()) { + return it->second.get(); } - - TRI_ASSERT((*it).second != nullptr); - return (*it).second.get(); + graph::GraphManager graphManager{_vocbase, _contextOwnedByExterior}; + + auto g = graphManager.lookupGraphByName(name); + + if (g.fail()) { + return nullptr; + } + + auto graph = g.get().get(); + _graphs.emplace(name, std::move(g.get())); + + return graph; } /// @brief returns the next query id diff --git a/arangod/Aql/Query.h b/arangod/Aql/Query.h index ec61dbec35..7b75fa0901 100644 --- a/arangod/Aql/Query.h +++ b/arangod/Aql/Query.h @@ -58,6 +58,10 @@ namespace velocypack { class Builder; } +namespace graph { +class Graph; +} + namespace aql { struct AstNode; @@ -280,7 +284,7 @@ class Query { std::string getStateString() const; /// @brief look up a graph in the _graphs collection - Graph const* lookupGraphByName(std::string const& name); + graph::Graph const* lookupGraphByName(std::string const& name); /// @brief return the bind parameters as passed by the user std::shared_ptr bindParameters() const { @@ -355,7 +359,7 @@ class Query { V8Context* _context; /// @brief graphs used in query, identified by name - std::unordered_map> _graphs; + std::unordered_map> _graphs; /// @brief the actual query string QueryString _queryString; diff --git a/arangod/Auth/User.cpp b/arangod/Auth/User.cpp index 5f72f2b357..ec90e64d52 100644 --- a/arangod/Auth/User.cpp +++ b/arangod/Auth/User.cpp @@ -556,7 +556,7 @@ auth::Level auth::User::collectionAuthLevel(std::string const& dbname, return auth::Level::NONE; // invalid collection names } // we must have got a non-empty collection name when we get here - TRI_ASSERT(cname[0] < '0' || cname[0] > '9'); + TRI_ASSERT(!isdigit(cname[0])); bool isSystem = cname[0] == '_'; if (isSystem) { diff --git a/arangod/Auth/UserManager.cpp b/arangod/Auth/UserManager.cpp index d39f47d287..be233ffc45 100644 --- a/arangod/Auth/UserManager.cpp +++ b/arangod/Auth/UserManager.cpp @@ -738,8 +738,9 @@ auth::Level auth::UserManager::collectionAuthLevel(std::string const& user, } auth::Level level; - if (coll[0] >= '0' && coll[0] <= '9') { - std::string tmpColl = DatabaseFeature::DATABASE->translateCollectionName(dbname, coll); + if (isdigit(coll[0])) { + std::string tmpColl = + DatabaseFeature::DATABASE->translateCollectionName(dbname, coll); level = it->second.collectionAuthLevel(dbname, tmpColl); } else { level = it->second.collectionAuthLevel(dbname, coll); diff --git a/arangod/CMakeLists.txt b/arangod/CMakeLists.txt index b6c74709f5..c45617f31f 100644 --- a/arangod/CMakeLists.txt +++ b/arangod/CMakeLists.txt @@ -323,6 +323,9 @@ SET(ARANGOD_SOURCES Graph/ConstantWeightShortestPathFinder.cpp Graph/ClusterTraverserCache.cpp Graph/EdgeCollectionInfo.cpp + Graph/Graph.cpp + Graph/GraphManager.cpp + Graph/GraphOperations.cpp Graph/NeighborsEnumerator.cpp Graph/PathEnumerator.cpp Graph/ShortestPathOptions.cpp @@ -404,6 +407,7 @@ SET(ARANGOD_SOURCES RestHandler/RestEndpointHandler.cpp RestHandler/RestEngineHandler.cpp RestHandler/RestExplainHandler.cpp + RestHandler/RestGraphHandler.cpp RestHandler/RestImportHandler.cpp RestHandler/RestIndexHandler.cpp RestHandler/RestJobHandler.cpp @@ -507,6 +511,7 @@ SET(ARANGOD_SOURCES V8Server/v8-vocbase.cpp V8Server/v8-voccursor.cpp V8Server/v8-vocindex.cpp + V8Server/v8-general-graph.cpp VocBase/Methods/AqlUserFunctions.cpp VocBase/Methods/Collections.cpp VocBase/Methods/Databases.cpp @@ -516,7 +521,6 @@ SET(ARANGOD_SOURCES VocBase/Methods/Upgrade.cpp VocBase/Methods/UpgradeTasks.cpp VocBase/Methods/Version.cpp - VocBase/Graphs.cpp VocBase/KeyGenerator.cpp VocBase/LogicalCollection.cpp VocBase/LogicalDataSource.cpp diff --git a/arangod/Cluster/ResultT.h b/arangod/Cluster/ResultT.h index 14e7e8203f..1538011cac 100644 --- a/arangod/Cluster/ResultT.h +++ b/arangod/Cluster/ResultT.h @@ -26,6 +26,7 @@ #include #include "Basics/Common.h" +#include "Basics/Result.h" namespace arangodb { @@ -57,7 +58,13 @@ namespace arangodb { template class ResultT : public arangodb::Result { public: - ResultT static success(T val) { return ResultT(val, TRI_ERROR_NO_ERROR); } + ResultT static success(T const& val) { + return ResultT(val, TRI_ERROR_NO_ERROR); + } + + ResultT static success(T&& val) { + return ResultT(std::move(val), TRI_ERROR_NO_ERROR); + } ResultT static error(int errorNumber) { return ResultT(boost::none, errorNumber); @@ -67,17 +74,23 @@ class ResultT : public arangodb::Result { return ResultT(boost::none, errorNumber, errorMessage); } - // This is not explicit on purpose - // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + // These are not explicit on purpose ResultT(Result const& other) : Result(other) { // .ok() is not allowed here, as _val should be expected to be initialized // iff .ok() is true. TRI_ASSERT(other.fail()); } - // This is not explicit on purpose - // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) - ResultT(T&& val) : ResultT(std::forward(val), TRI_ERROR_NO_ERROR) {} + ResultT(Result&& other) : Result(std::move(other)) { + // .ok() is not allowed here, as _val should be expected to be initialized + // iff .ok() is true. + TRI_ASSERT(other.fail()); + } + + // These are not explicit on purpose + ResultT(T&& val) : ResultT(std::move(val), TRI_ERROR_NO_ERROR) {} + + ResultT(T const& val) : ResultT(val, TRI_ERROR_NO_ERROR) {} ResultT() = delete; @@ -91,6 +104,8 @@ class ResultT : public arangodb::Result { return *this; } + Result copy_result() const { return *this; } + // These would be very convenient, but also make it very easy to accidentally // use the value of an error-result. So don't add them. // @@ -139,11 +154,20 @@ class ResultT : public arangodb::Result { boost::optional _val; ResultT(boost::optional&& val_, int errorNumber) - : Result(errorNumber), _val(val_) {} + : Result(errorNumber), _val(std::move(val_)) {} ResultT(boost::optional&& val_, int errorNumber, std::string const& errorMessage) - : Result(errorNumber, errorMessage), _val(val_) {} + : Result(errorNumber, errorMessage), + _val(val_) {} + + ResultT(boost::optionalconst& val_, int errorNumber) + : Result(errorNumber), _val(std::move(val_)) {} + + ResultT(boost::optionalconst& val_, int errorNumber, + std::string const& errorMessage) + : Result(errorNumber, errorMessage), + _val(val_) {} }; } // namespace arangodb diff --git a/arangod/GeneralServer/GeneralServerFeature.cpp b/arangod/GeneralServer/GeneralServerFeature.cpp index 97332d6c5d..27b44335ef 100644 --- a/arangod/GeneralServer/GeneralServerFeature.cpp +++ b/arangod/GeneralServer/GeneralServerFeature.cpp @@ -38,6 +38,7 @@ #include "GeneralServer/AuthenticationFeature.h" #include "GeneralServer/GeneralServer.h" #include "GeneralServer/RestHandlerFactory.h" +#include "Graph/Graph.h" #include "InternalRestHandler/InternalRestTraverserHandler.h" #include "ProgramOptions/Parameters.h" #include "ProgramOptions/ProgramOptions.h" @@ -60,6 +61,7 @@ #include "RestHandler/RestEndpointHandler.h" #include "RestHandler/RestEngineHandler.h" #include "RestHandler/RestExplainHandler.h" +#include "RestHandler/RestGraphHandler.h" #include "RestHandler/RestHandlerCreator.h" #include "RestHandler/RestImportHandler.h" #include "RestHandler/RestIndexHandler.h" @@ -68,9 +70,9 @@ #include "RestHandler/RestPregelHandler.h" #include "RestHandler/RestQueryCacheHandler.h" #include "RestHandler/RestQueryHandler.h" +#include "RestHandler/RestRepairHandler.h" #include "RestHandler/RestShutdownHandler.h" #include "RestHandler/RestSimpleHandler.h" -#include "RestHandler/RestRepairHandler.h" #include "RestHandler/RestSimpleQueryHandler.h" #include "RestHandler/RestStatusHandler.h" #include "RestHandler/RestTasksHandler.h" @@ -335,6 +337,10 @@ void GeneralServerFeature::defineHandlers() { RestVocbaseBaseHandler::EDGES_PATH, RestHandlerCreator::createNoData); + _handlerFactory->addPrefixHandler( + RestVocbaseBaseHandler::GHARIAL_PATH, + RestHandlerCreator::createNoData); + _handlerFactory->addPrefixHandler( RestVocbaseBaseHandler::ENDPOINT_PATH, RestHandlerCreator::createNoData); diff --git a/arangod/GeneralServer/GeneralServerFeature.h b/arangod/GeneralServer/GeneralServerFeature.h index fc9e78a628..26542e7e07 100644 --- a/arangod/GeneralServer/GeneralServerFeature.h +++ b/arangod/GeneralServer/GeneralServerFeature.h @@ -84,7 +84,7 @@ class GeneralServerFeature final return GENERAL_SERVER->_accessControlAllowOrigins; } - + private: static GeneralServerFeature* GENERAL_SERVER; @@ -98,7 +98,7 @@ class GeneralServerFeature final void start() override final; void stop() override final; void unprepare() override final; - + private: double _keepAliveTimeout = 300.0; bool _allowMethodOverride; @@ -118,7 +118,9 @@ class GeneralServerFeature final private: std::unique_ptr _handlerFactory; std::unique_ptr _jobManager; - std::unique_ptr> _combinedRegistries; + std::unique_ptr< + std::pair> + _combinedRegistries; std::vector _servers; }; } diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index a031748180..a6239f74c0 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -34,7 +34,7 @@ namespace arangodb { class GeneralRequest; class RequestStatistics; -enum class RestStatus { DONE, WAITING, FAIL}; +enum class RestStatus { DONE, WAITING, FAIL }; namespace rest { class RestHandler : public std::enable_shared_from_this { diff --git a/arangod/Graph/Graph.cpp b/arangod/Graph/Graph.cpp new file mode 100644 index 0000000000..d0d1e7a06e --- /dev/null +++ b/arangod/Graph/Graph.cpp @@ -0,0 +1,534 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Tobias Gödderz +//////////////////////////////////////////////////////////////////////////////// + +#include "Graph.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Aql/AstNode.h" +#include "Aql/Graphs.h" +#include "Aql/Query.h" +#include "Basics/ReadLocker.h" +#include "Basics/StaticStrings.h" +#include "Basics/VelocyPackHelper.h" +#include "Basics/WriteLocker.h" +#include "Cluster/ServerState.h" +#include "RestServer/QueryRegistryFeature.h" +#include "Transaction/Methods.h" +#include "Transaction/StandaloneContext.h" +#include "Utils/OperationOptions.h" +#include "Utils/SingleCollectionTransaction.h" +#include "VocBase/LogicalCollection.h" +#include "VocBase/Methods/Collections.h" + +using namespace arangodb; +using namespace arangodb::graph; +using UserTransaction = transaction::Methods; +using VelocyPackHelper = basics::VelocyPackHelper; + +#ifndef USE_ENTERPRISE +// Factory methods +std::unique_ptr Graph::fromPersistence(VPackSlice document, TRI_vocbase_t& vocbase) { + std::unique_ptr result{new Graph{document}}; + return result; +} + +std::unique_ptr Graph::fromUserInput(std::string&& name, VPackSlice document, VPackSlice options) { + std::unique_ptr result{new Graph{std::move(name), document, options}}; + return result; +} +#endif + +std::unique_ptr Graph::fromUserInput(std::string const& name, VPackSlice document, VPackSlice options) { + return Graph::fromUserInput(std::string{name}, document, options); + +} + +// From persistence +Graph::Graph(velocypack::Slice const& slice) + : _graphName(VelocyPackHelper::getStringValue(slice, StaticStrings::KeyString, "")), + _vertexColls(), + _edgeColls(), + _numberOfShards(basics::VelocyPackHelper::readNumericValue( + slice, StaticStrings::NumberOfShards, 1)), + _replicationFactor(basics::VelocyPackHelper::readNumericValue( + slice, StaticStrings::ReplicationFactor, 1)), + _rev(basics::VelocyPackHelper::getStringValue( + slice, StaticStrings::RevString, "")) { + // If this happens we have a document without an _key Attribute. + TRI_ASSERT(!_graphName.empty()); + if (_graphName.empty()) { + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "Persisted graph is invalid. It does not have a _key set. Please contact support."); + } + + // If this happens we have a document without an _rev Attribute. + TRI_ASSERT(!_rev.empty()); + if (_rev.empty()) { + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "Persisted graph is invalid. It does not have a _rev set. Please contact support."); + } + + if (slice.hasKey(StaticStrings::GraphEdgeDefinitions)) { + parseEdgeDefinitions(slice.get(StaticStrings::GraphEdgeDefinitions)); + } + if (slice.hasKey(StaticStrings::GraphOrphans)) { + insertOrphanCollections(slice.get(StaticStrings::GraphOrphans)); + } +} + +// From user input +Graph::Graph(std::string&& graphName, VPackSlice const& info, VPackSlice const& options) + : _graphName(graphName), + _vertexColls(), + _edgeColls(), + _numberOfShards(1), + _replicationFactor(1), + _rev("") { + if (_graphName.empty()) { + THROW_ARANGO_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + TRI_ASSERT(_rev.empty()); + + if (info.hasKey(StaticStrings::GraphEdgeDefinitions)) { + parseEdgeDefinitions(info.get(StaticStrings::GraphEdgeDefinitions)); + } + if (info.hasKey(StaticStrings::GraphOrphans)) { + insertOrphanCollections(info.get(StaticStrings::GraphOrphans)); + } + if (options.isObject()) { + _numberOfShards = VelocyPackHelper::readNumericValue( + options, StaticStrings::NumberOfShards, 1); + _replicationFactor = VelocyPackHelper::readNumericValue( + options, StaticStrings::ReplicationFactor, 1); + } +} + +void Graph::parseEdgeDefinitions(VPackSlice edgeDefs) { + TRI_ASSERT(edgeDefs.isArray()); + if (!edgeDefs.isArray()) { + THROW_ARANGO_EXCEPTION_MESSAGE( + TRI_ERROR_GRAPH_INVALID_GRAPH, + "'edgeDefinitions' are not an array in the graph definition"); + } + + for (auto const& def : VPackArrayIterator(edgeDefs)) { + auto edgeDefRes = addEdgeDefinition(def); + if (edgeDefRes.fail()) { + THROW_ARANGO_EXCEPTION(edgeDefRes.copy_result()); + } + } +} + +void Graph::insertOrphanCollections(VPackSlice const arr) { + TRI_ASSERT(arr.isArray()); + for (auto const& c : VPackArrayIterator(arr)) { + TRI_ASSERT(c.isString()); + addOrphanCollection(c.copyString()); + } +} + +std::unordered_set const& Graph::vertexCollections() const { + return _vertexColls; +} + +std::set const& Graph::orphanCollections() const { + return _orphanColls; +} + +std::set const& Graph::edgeCollections() const { + return _edgeColls; +} + +std::map const& Graph::edgeDefinitions() + const { + return _edgeDefs; +} + +uint64_t Graph::numberOfShards() const { return _numberOfShards; } + +uint64_t Graph::replicationFactor() const { return _replicationFactor; } + +std::string const Graph::id() const { + return std::string(StaticStrings::GraphCollection + "/" + _graphName); +} + +std::string const& Graph::rev() const { return _rev; } + +void Graph::addVertexCollection(std::string const& name) { + if (_orphanColls.find(name) != _orphanColls.end()) { + // Promote Orphans to vertices + _orphanColls.erase(name); + } + _vertexColls.emplace(name); +} + +Result Graph::addOrphanCollection(std::string&& name) { + if (_vertexColls.find(name) != _vertexColls.end()) { + return TRI_ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF; + } + TRI_ASSERT(_orphanColls.find(name) == _orphanColls.end()); + _vertexColls.emplace(name); + _orphanColls.emplace(std::move(name)); + return TRI_ERROR_NO_ERROR; +} + +void Graph::setSmartState(bool state) { _isSmart = state; } + +void Graph::setNumberOfShards(uint64_t numberOfShards) { + _numberOfShards = numberOfShards; +} + +void Graph::setReplicationFactor(uint64_t replicationFactor) { + _replicationFactor = replicationFactor; +} + +void Graph::setRev(std::string&& rev) { _rev = std::move(rev); } + +void Graph::toVelocyPack(VPackBuilder& builder) const { + VPackObjectBuilder guard(&builder); + + if (!_vertexColls.empty()) { + builder.add(VPackValue("vertexCollectionNames")); + VPackArrayBuilder guard2(&builder); + for (auto const& cn : _vertexColls) { + builder.add(VPackValue(cn)); + } + } + + if (!_edgeColls.empty()) { + builder.add(VPackValue("edgeCollectionNames")); + VPackArrayBuilder guard2(&builder); + for (auto const& cn : _edgeColls) { + builder.add(VPackValue(cn)); + } + } +} + +void Graph::toPersistence(VPackBuilder& builder) const { + TRI_ASSERT(builder.isOpenObject()); + + // The name + builder.add(StaticStrings::KeyString, VPackValue(_graphName)); + + // Cluster Information + builder.add(StaticStrings::NumberOfShards, VPackValue(_numberOfShards)); + builder.add(StaticStrings::ReplicationFactor, VPackValue(_replicationFactor)); + builder.add(StaticStrings::GraphIsSmart, VPackValue(isSmart())); + + // EdgeDefinitions + builder.add(VPackValue(StaticStrings::GraphEdgeDefinitions)); + builder.openArray(); + for (auto const& it : edgeDefinitions()) { + it.second.addToBuilder(builder); + } + builder.close(); // EdgeDefinitions + + // Orphan Collections + builder.add(VPackValue(StaticStrings::GraphOrphans)); + builder.openArray(); + for (auto const& on : _orphanColls) { + builder.add(VPackValue(on)); + } + builder.close(); // Orphans +} + +void Graph::enhanceEngineInfo(VPackBuilder&) const {} + +// validates the type: +// edgeDefinition : { collection : string, from : [string], to : [string] } +Result EdgeDefinition::validateEdgeDefinition( + VPackSlice const& edgeDefinition) { + if (!edgeDefinition.isObject()) { + return Result(TRI_ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION); + } + + for (auto const& key : std::array{ + {"collection", StaticStrings::GraphFrom, StaticStrings::GraphTo}}) { + if (!edgeDefinition.hasKey(key)) { + return Result(TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT, + "Attribute '" + key + "' missing in edge definition!"); + } + } + + if (!edgeDefinition.get("collection").isString()) { + return Result(TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT, + "edge definition is not a string!"); + } + + for (auto const& key : std::array{ + {StaticStrings::GraphFrom, StaticStrings::GraphTo}}) { + if (!edgeDefinition.get(key).isArray()) { + return Result(TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT, + "Edge definition '" + key + "' is not an array!"); + } + + for (auto const& it : VPackArrayIterator(edgeDefinition.get(key))) { + if (!it.isString()) { + return Result(TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT, + std::string("Edge definition '") + key + + "' does not only contain strings!"); + } + } + } + + return Result(); +} + +// TODO: maybe create a class instance here + func as class func +// sort an edgeDefinition: +// edgeDefinition : { collection : string, from : [string], to : [string] } +std::shared_ptr> EdgeDefinition::sortEdgeDefinition( + VPackSlice const& edgeDefinition) { + arangodb::basics::VelocyPackHelper::VPackLess sorter; + VPackBuilder from = VPackCollection::sort(edgeDefinition.get(StaticStrings::GraphFrom), sorter); + VPackBuilder to = VPackCollection::sort(edgeDefinition.get(StaticStrings::GraphTo), sorter); + VPackBuilder sortedBuilder; + sortedBuilder.openObject(); + sortedBuilder.add("collection", edgeDefinition.get("collection")); + sortedBuilder.add(StaticStrings::GraphFrom, from.slice()); + sortedBuilder.add(StaticStrings::GraphTo, to.slice()); + sortedBuilder.close(); + + return sortedBuilder.steal(); +} + +ResultT EdgeDefinition::createFromVelocypack( + VPackSlice edgeDefinition) { + Result res = EdgeDefinition::validateEdgeDefinition(edgeDefinition); + if (res.fail()) { + return res; + } + std::string collection = edgeDefinition.get("collection").copyString(); + VPackSlice from = edgeDefinition.get(StaticStrings::GraphFrom); + VPackSlice to = edgeDefinition.get(StaticStrings::GraphTo); + + std::set fromSet; + std::set toSet; + + // duplicates in from and to shouldn't occur, but are safely ignored here + for (auto const& it : VPackArrayIterator(from)) { + fromSet.emplace(it.copyString()); + } + for (auto const& it : VPackArrayIterator(to)) { + toSet.emplace(it.copyString()); + } + + return EdgeDefinition{collection, std::move(fromSet), std::move(toSet)}; +} + +bool EdgeDefinition::operator==(EdgeDefinition const& other) const { + return this->getName() == other.getName() && + this->getFrom() == other.getFrom() && this->getTo() == other.getTo(); +} + +bool EdgeDefinition::operator!=(EdgeDefinition const& other) const { + return this->getName() != other.getName() || + this->getFrom() != other.getFrom() || this->getTo() != other.getTo(); +} + +void EdgeDefinition::addToBuilder(VPackBuilder& builder) const { + builder.add(VPackValue(VPackValueType::Object)); + builder.add("collection", VPackValue(getName())); + + builder.add("from", VPackValue(VPackValueType::Array)); + for (auto const& from : getFrom()) { + builder.add(VPackValue(from)); + } + builder.close(); // from + + // to + builder.add("to", VPackValue(VPackValueType::Array)); + for (auto const& to : getTo()) { + builder.add(VPackValue(to)); + } + builder.close(); // to + + builder.close(); // obj +} + +bool EdgeDefinition::hasFrom(std::string const &vertexCollection) const { + return getFrom().find(vertexCollection) != getFrom().end(); +} + +bool EdgeDefinition::hasTo(std::string const &vertexCollection) const { + return getTo().find(vertexCollection) != getTo().end(); +} + +bool EdgeDefinition::hasVertexCollection(const std::string &vertexCollection) const { + return hasFrom(vertexCollection) || hasTo(vertexCollection); +} + +// validates the type: +// orphanDefinition : string +Result Graph::validateOrphanCollection(VPackSlice const& orphanCollection) { + if (!orphanCollection.isString()) { + return Result(TRI_ERROR_GRAPH_CREATE_MALFORMED_ORPHAN_LIST, + "orphan collection is not a string!"); + } + return Result(); +} + +ResultT Graph::addEdgeDefinition(VPackSlice const& edgeDefinitionSlice) { + auto res = EdgeDefinition::createFromVelocypack(edgeDefinitionSlice); + + if (res.fail()) { + return res.copy_result(); + } + TRI_ASSERT(res.ok()); + + EdgeDefinition const& edgeDefinition = res.get(); + + std::string const& collection = edgeDefinition.getName(); + if (hasEdgeCollection(collection)) { + return {Result( + TRI_ERROR_GRAPH_COLLECTION_MULTI_USE, + collection + " " + std::string{TRI_errno_string( + TRI_ERROR_GRAPH_COLLECTION_MULTI_USE)})}; + } + + _edgeColls.emplace(collection); + _edgeDefs.emplace(collection, edgeDefinition); + TRI_ASSERT(hasEdgeCollection(collection)); + for (auto const& it : edgeDefinition.getFrom()) { + addVertexCollection(it); + } + for (auto const& it : edgeDefinition.getTo()) { + addVertexCollection(it); + } + + return &_edgeDefs.find(collection)->second; +} + +std::ostream& Graph::operator<<(std::ostream& ostream) { + ostream << "Graph \"" << name() << "\" {\n"; + for (auto const& it : _edgeDefs) { + EdgeDefinition const& def = it.second; + ostream << " collection \"" << def.getName() << "\" {\n"; + ostream << " from ["; + bool first = true; + for (auto const& from : def.getFrom()) { + if (!first) { + ostream << ", "; + } + first = false; + ostream << from; + } + ostream << " to ["; + first = true; + for (auto const& to : def.getTo()) { + if (!first) { + ostream << ", "; + } + first = false; + ostream << to; + } + ostream << " }\n"; + } + ostream << "}"; + + return ostream; +} + +bool Graph::hasEdgeCollection(std::string const& collectionName) const { + TRI_ASSERT( + (edgeDefinitions().find(collectionName) != edgeDefinitions().end()) == + (edgeCollections().find(collectionName) != edgeCollections().end())); + return edgeCollections().find(collectionName) != edgeCollections().end(); +} + +bool Graph::hasVertexCollection(std::string const& collectionName) const { + return vertexCollections().find(collectionName) != vertexCollections().end(); +} + +bool Graph::hasOrphanCollection(std::string const& collectionName) const { + return orphanCollections().find(collectionName) != orphanCollections().end(); +} + +void Graph::graphForClient(VPackBuilder& builder) const { + TRI_ASSERT(builder.isOpenObject()); + builder.add(VPackValue("graph")); + builder.openObject(); + + toPersistence(builder); + TRI_ASSERT(builder.isOpenObject()); + builder.add(StaticStrings::RevString, VPackValue(rev())); + builder.add(StaticStrings::IdString, VPackValue(id())); + builder.add(StaticStrings::GraphName, VPackValue(_graphName)); + builder.close(); // graph object +} + +Result Graph::validateCollection(LogicalCollection& col) const { + return {TRI_ERROR_NO_ERROR}; +} + +void Graph::edgesToVpack(VPackBuilder& builder) const { + builder.add(VPackValue(VPackValueType::Object)); + builder.add("collections", VPackValue(VPackValueType::Array)); + + for (auto const& edgeCollection : edgeCollections()) { + builder.add(VPackValue(edgeCollection)); + } + builder.close(); + + builder.close(); +} + +void Graph::verticesToVpack(VPackBuilder& builder) const { + builder.add(VPackValue(VPackValueType::Object)); + builder.add("collections", VPackValue(VPackValueType::Array)); + + for (auto const& vertexCollection : vertexCollections()) { + builder.add(VPackValue(vertexCollection)); + } + builder.close(); + + builder.close(); +} + +bool Graph::isSmart() const { + return false; +} + +void Graph::createCollectionOptions(VPackBuilder& builder, bool waitForSync) const { + TRI_ASSERT(builder.isOpenObject()); + + builder.add(StaticStrings::WaitForSyncString, VPackValue(waitForSync)); + builder.add(StaticStrings::NumberOfShards, VPackValue(numberOfShards())); + builder.add(StaticStrings::ReplicationFactor, VPackValue(replicationFactor())); +} + +boost::optional Graph::getEdgeDefinition( + std::string const& collectionName) const { + auto it = edgeDefinitions().find(collectionName); + if (it == edgeDefinitions().end()) { + TRI_ASSERT(!hasEdgeCollection(collectionName)); + return boost::none; + } + + TRI_ASSERT(hasEdgeCollection(collectionName)); + return {it->second}; +} diff --git a/arangod/Graph/Graph.h b/arangod/Graph/Graph.h new file mode 100644 index 0000000000..6de38d85a9 --- /dev/null +++ b/arangod/Graph/Graph.h @@ -0,0 +1,288 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Tobias Gödderz +//////////////////////////////////////////////////////////////////////////////// + +#ifndef ARANGOD_GRAPH_GRAPH_H +#define ARANGOD_GRAPH_GRAPH_H + +#include +#include +#include + +#include "Aql/Query.h" +#include "Aql/VariableGenerator.h" +#include "Basics/ReadWriteLock.h" +#include "Cluster/ClusterInfo.h" +#include "Cluster/ResultT.h" +#include "Transaction/Methods.h" +#include "Transaction/StandaloneContext.h" +#include "Utils/OperationResult.h" + +namespace arangodb { +namespace graph { + +class EdgeDefinition { + public: + EdgeDefinition(std::string edgeCollection_, std::set&& from_, + std::set&& to_) + : _edgeCollection(std::move(edgeCollection_)), _from(from_), _to(to_) {} + + std::string const& getName() const { return _edgeCollection; } + std::set const& getFrom() const { return _from; } + std::set const& getTo() const { return _to; } + + /// @brief Adds the edge definition as a new object {collection, from, to} + /// to the builder. + void addToBuilder(velocypack::Builder& builder) const; + + bool hasFrom(std::string const& vertexCollection) const; + bool hasTo(std::string const& vertexCollection) const; + + bool hasVertexCollection(std::string const& vertexCollection) const; + + /// @brief validate the structure of edgeDefinition, i.e. + /// that it contains the correct attributes, and that they contain the correct + /// types of values. + static Result validateEdgeDefinition(const velocypack::Slice& edgeDefinition); + static std::shared_ptr> sortEdgeDefinition( + const velocypack::Slice& edgeDefinition); + + static ResultT createFromVelocypack( + velocypack::Slice edgeDefinition); + + bool operator==(EdgeDefinition const& other) const; + bool operator!=(EdgeDefinition const& other) const; + + private: + std::string _edgeCollection; + std::set _from; + std::set _to; +}; + +class Graph { + + public: + + /** + * @brief Create graph from persistence. + * + * @param document The stored document + * + * @return A graph object corresponding to this document + */ + static std::unique_ptr fromPersistence( + velocypack::Slice document, TRI_vocbase_t& vocbase); + + /** + * @brief Create graph from user input. + * NOTE: This is purely in memory and will NOT persist anything. + * + * @param name The name of the Graph + * @param collectionInformation Collection information about relations and orphans + * @param options The collection creation options. + * + * @return A graph object corresponding to the user input + */ + static std::unique_ptr fromUserInput( + std::string&& name, velocypack::Slice collectionInformation, + velocypack::Slice options); + + // Wrapper for Move constructor + static std::unique_ptr fromUserInput( + std::string const& name, velocypack::Slice collectionInformation, + velocypack::Slice options); + + protected: + + /** + * @brief Create graph from persistence. + * + * @param info The stored document + */ + explicit Graph(velocypack::Slice const& info); + + + /** + * @brief Create graph from user input. + * + * @param graphName The name of the graph + * @param info Collection information, including relations and orphans + * @param options The options to be used for collections + */ + Graph(std::string&& graphName, velocypack::Slice const& info, velocypack::Slice const& options); + + public: + virtual ~Graph() = default; + + static Result validateOrphanCollection( + const velocypack::Slice& orphanDefinition); + + virtual void createCollectionOptions(VPackBuilder& builder, + bool waitForSync) const; + + public: + /// @brief get the cids of all vertexCollections + std::unordered_set const& vertexCollections() const; + + /// @brief get the cids of all orphanCollections + std::set const& orphanCollections() const; + + /// @brief get the cids of all edgeCollections + std::set const& edgeCollections() const; + + /// @brief get the cids of all edgeCollections + std::map const& edgeDefinitions() const; + + bool hasEdgeCollection(std::string const& collectionName) const; + bool hasVertexCollection(std::string const& collectionName) const; + bool hasOrphanCollection(std::string const& collectionName) const; + + boost::optional getEdgeDefinition( + std::string const& collectionName) const; + + virtual bool isSmart() const; + + uint64_t numberOfShards() const; + uint64_t replicationFactor() const; + std::string const id() const; + std::string const& rev() const; + + std::string const& name() const { return _graphName; } + + /// @brief return a VelocyPack representation of the graph + void toVelocyPack(velocypack::Builder&) const; + + /** + * @brief Create the GraphDocument to be stored in the database. + * + * @param builder The builder the result should be written in. Expects an open object. + */ + virtual void toPersistence(velocypack::Builder& builder) const; + + /** + * @brief Create the Graph Json Representation to be given to the client. + * Uses toPersistence, but also includes _rev and _id values and encapsulates + * the date into a graph attribute. + * + * @param builder The builder the result should be written in. Expects an open object. + */ + void graphForClient(VPackBuilder& builder) const; + + /** + * @brief Check if the collection is allowed to be used + * within this graph + * + * @param col The collection + * + * @return TRUE if we are safe to use it. + */ + virtual Result validateCollection(LogicalCollection& col) const; + + void edgesToVpack(VPackBuilder& builder) const; + void verticesToVpack(VPackBuilder& builder) const; + + virtual void enhanceEngineInfo(velocypack::Builder&) const; + + /// @brief adds one edge definition. Returns an error if the edgeDefinition + /// is already added to this graph. + ResultT addEdgeDefinition(velocypack::Slice const& edgeDefinitionSlice); + + /// @brief Add an orphan vertex collection to this graphs definition + Result addOrphanCollection(std::string&&); + + std::ostream& operator<<(std::ostream& ostream); + + private: + /// @brief Parse the edgeDefinition slice and inject it into this graph + void parseEdgeDefinitions(velocypack::Slice edgeDefs); + + /// @brief Add a vertex collection to this graphs definition + void addVertexCollection(std::string const&); + + /// @brief Add orphanCollections to the object + void insertOrphanCollections(velocypack::Slice arr); + + /// @brief Set numberOfShards to the graph definition + void setNumberOfShards(uint64_t numberOfShards); + + /// @brief Set replicationFactor to the graph definition + void setReplicationFactor(uint64_t setReplicationFactor); + + /// @brief Set isSmart to the graph definition + void setSmartState(bool state); + + /// @brief Set rev to the graph definition + void setRev(std::string&& rev); + +///////////////////////////////////////////////////////////////////////////////// +// +// SECTION: Variables +// +///////////////////////////////////////////////////////////////////////////////// + protected: + /// @brief name of this graph + std::string const _graphName; + + /// @brief the names of all vertexCollections + /// This includes orphans. + std::unordered_set _vertexColls; + + /// @brief the names of all orphanCollections + std::set _orphanColls; + + /// @brief the names of all edgeCollections + std::set _edgeColls; + + /// @brief edge definitions of this graph + std::map _edgeDefs; + + /// @brief state if smart graph enabled + bool _isSmart; + + /// @brief number of shards of this graph + uint64_t _numberOfShards; + + /// @brief replication factor of this graph + uint64_t _replicationFactor; + + /// @brief revision of this graph + std::string _rev; +}; + +// helper functions +template +void setUnion(std::set &set, C const &container) { + for(auto const& it : container) { + set.insert(it); + } +} + +template +void setMinus(std::set &set, C const &container) { + for(auto const& it : container) { + set.erase(it); + } +} + +} // namespace graph +} // namespace arangodb + +#endif // ARANGOD_GRAPH_GRAPH_H diff --git a/arangod/Graph/GraphCache.cpp b/arangod/Graph/GraphCache.cpp new file mode 100644 index 0000000000..0e711019fb --- /dev/null +++ b/arangod/Graph/GraphCache.cpp @@ -0,0 +1,182 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Tobias Gödderz +//////////////////////////////////////////////////////////////////////////////// + +#include "GraphCache.h" + +#include "Graph.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Aql/AstNode.h" +#include "Aql/Graphs.h" +#include "Aql/Query.h" +#include "Basics/ReadLocker.h" +#include "Basics/StaticStrings.h" +#include "Basics/VelocyPackHelper.h" +#include "Basics/WriteLocker.h" +#include "Graph/GraphManager.h" +#include "RestServer/QueryRegistryFeature.h" +#include "Transaction/Methods.h" +#include "Transaction/StandaloneContext.h" +#include "Utils/OperationOptions.h" +#include "Utils/SingleCollectionTransaction.h" +#include "VocBase/LogicalCollection.h" +#include "VocBase/Methods/Collections.h" + +using namespace arangodb; +using namespace arangodb::graph; + +namespace getGraphFromCacheResult { +struct Success { + std::shared_ptr graph; + + explicit Success(std::shared_ptr graph_) + : graph(std::move(graph_)){}; + Success& operator=(Success const& other) = default; + Success() = delete; +}; +struct Outdated {}; +struct NotFound {}; +struct Exception {}; +} + +using GetGraphFromCacheResult = boost::variant< + getGraphFromCacheResult::Success, getGraphFromCacheResult::Outdated, + getGraphFromCacheResult::NotFound, getGraphFromCacheResult::Exception>; + +GetGraphFromCacheResult getGraphFromCache(GraphCache::CacheType const &_cache, + std::string const &name, + std::chrono::seconds maxAge) { + using namespace getGraphFromCacheResult; + + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + + GraphCache::CacheType::const_iterator entryIt; + bool entryFound; + try { + entryIt = _cache.find(name); + entryFound = entryIt != _cache.end(); + } catch (...) { + return Exception{}; + } + + if (!entryFound) { + return NotFound{}; + } + + GraphCache::EntryType const& entry = entryIt->second; + std::chrono::steady_clock::time_point const& insertedAt = entry.first; + + if (now - insertedAt > maxAge) { + return Outdated{}; + } + + return Success{entry.second}; +} + +const std::shared_ptr GraphCache::getGraph( + std::shared_ptr ctx, std::string const& name, + std::chrono::seconds maxAge) { + using namespace getGraphFromCacheResult; + + GetGraphFromCacheResult cacheResult = Exception{}; + + // try to lookup the graph in the cache first + { + READ_LOCKER(guard, _lock); + cacheResult = getGraphFromCache(_cache, name, maxAge); + } + + // TODO The cache saves the graph names globally, not per database! + // This must be addressed as soon as it is activated. + /* + if (typeid(Success) == cacheResult.type()) { + LOG_TOPIC(TRACE, Logger::GRAPHS) << "GraphCache::getGraph('" << name + << "'): Found entry in cache"; + + return boost::get(cacheResult).graph; + } else if (typeid(Outdated) == cacheResult.type()) { + LOG_TOPIC(TRACE, Logger::GRAPHS) << "GraphCache::getGraph('" << name + << "'): Cached entry outdated"; + } else if (typeid(NotFound) == cacheResult.type()) { + LOG_TOPIC(TRACE, Logger::GRAPHS) << "GraphCache::getGraph('" << name + << "'): No cache entry"; + } else if (typeid(Exception) == cacheResult.type()) { + LOG_TOPIC(ERR, Logger::GRAPHS) + << "GraphCache::getGraph('" << name + << "'): An exception occured during cache lookup"; + } else { + LOG_TOPIC(FATAL, Logger::GRAPHS) << "GraphCache::getGraph('" << name + << "'): Unhandled result type " + << cacheResult.type().name(); + + return nullptr; + }*/ + + // if the graph wasn't found in the cache, lookup the graph and insert or + // replace the entry. if the graph doesn't exist, erase a possible entry from + // the cache. + std::unique_ptr graph; + try { + WRITE_LOCKER(guard, _lock); + + std::chrono::steady_clock::time_point now = + std::chrono::steady_clock::now(); + + GraphManager gmngr{ctx->vocbase(), true}; + auto result = gmngr.lookupGraphByName(name); + if (result.fail()) { + if (result.is(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND) || + result.is(TRI_ERROR_GRAPH_NOT_FOUND)) { + _cache.erase(name); + } + } else { + graph.reset(result.get()); + } + + if (graph == nullptr) { + return nullptr; + } + + CacheType::iterator it; + bool insertSuccess; + std::tie(it, insertSuccess) = + _cache.emplace(name, std::make_pair(now, graph)); + + if (!insertSuccess) { + it->second.first = now; + it->second.second = graph; + } + + } catch (...) { + }; + + // graph is never set to an invalid or outdated value. So even in case of an + // exception, if graph was set, it may be returned. + return graph; +} diff --git a/arangod/Graph/GraphCache.h b/arangod/Graph/GraphCache.h new file mode 100644 index 0000000000..33680c74d6 --- /dev/null +++ b/arangod/Graph/GraphCache.h @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Tobias Gödderz +//////////////////////////////////////////////////////////////////////////////// + +#ifndef ARANGOD_GRAPH_GRAPHCACHE_H +#define ARANGOD_GRAPH_GRAPHCACHE_H + +#include +#include +#include + +#include "Aql/Query.h" +#include "Aql/VariableGenerator.h" +#include "Basics/ReadWriteLock.h" +#include "Transaction/Methods.h" +#include "Transaction/StandaloneContext.h" + +namespace arangodb { +namespace graph { +class GraphCache { + public: + // save now() along with the graph + using EntryType = std::pair>; + using CacheType = std::unordered_map; + + // TODO The cache saves the graph names globally, not per database! + // This must be addressed as soon as it is activated. + const std::shared_ptr getGraph( + std::shared_ptr ctx, std::string const& name, + std::chrono::seconds maxAge = std::chrono::seconds(60)); + + private: + basics::ReadWriteLock _lock; + CacheType _cache; +}; +} // namespace graph +} // namespace arangodb + +#endif // ARANGOD_GRAPH_GRAPHCACHE_H diff --git a/arangod/Graph/GraphManager.cpp b/arangod/Graph/GraphManager.cpp new file mode 100644 index 0000000000..ca9c493302 --- /dev/null +++ b/arangod/Graph/GraphManager.cpp @@ -0,0 +1,1003 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Heiko Kernbach +//////////////////////////////////////////////////////////////////////////////// + +#include "GraphOperations.h" +#include "GraphManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Aql/AstNode.h" +#include "Aql/Graphs.h" +#include "Aql/Query.h" +#include "Basics/ReadLocker.h" +#include "Basics/StaticStrings.h" +#include "Basics/VelocyPackHelper.h" +#include "Basics/WriteLocker.h" +#include "Cluster/ClusterInfo.h" +#include "Cluster/ServerState.h" +#include "Graph/Graph.h" +#include "RestServer/QueryRegistryFeature.h" +#include "Transaction/Methods.h" +#include "Transaction/V8Context.h" +#include "Transaction/StandaloneContext.h" +#include "Utils/ExecContext.h" +#include "Utils/OperationOptions.h" +#include "Utils/SingleCollectionTransaction.h" +#include "VocBase/LogicalCollection.h" +#include "VocBase/Methods/Collections.h" + +using namespace arangodb; +using namespace arangodb::graph; +using UserTransaction = transaction::Methods; +using VelocyPackHelper = basics::VelocyPackHelper; + +namespace { +static bool ArrayContainsCollection(VPackSlice array, std::string const& colName) { + TRI_ASSERT(array.isArray()); + for (auto const& it : VPackArrayIterator(array)) { + if (it.copyString() == colName) { + return true; + } + } + return false; +} +} + + +std::shared_ptr GraphManager::ctx() const { + if (_isInTransaction) { + // we must use v8 + return transaction::V8Context::Create(_vocbase, true); + } + + return transaction::StandaloneContext::Create(_vocbase); +}; + +OperationResult GraphManager::createEdgeCollection(std::string const& name, + bool waitForSync, + VPackSlice options) { + return createCollection(name, TRI_COL_TYPE_EDGE, waitForSync, options); +} + +OperationResult GraphManager::createVertexCollection(std::string const& name, + bool waitForSync, + VPackSlice options) { + return createCollection(name, TRI_COL_TYPE_DOCUMENT, waitForSync, options); +} + +OperationResult GraphManager::createCollection(std::string const& name, + TRI_col_type_e colType, + bool waitForSync, + VPackSlice options) { + TRI_ASSERT(colType == TRI_COL_TYPE_DOCUMENT || colType == TRI_COL_TYPE_EDGE); + + + Result res = methods::Collections::create(&ctx()->vocbase(), name, colType, + options, waitForSync, true, + [&](LogicalCollection& coll) {}); + + return OperationResult(res); +} + +OperationResult GraphManager::findOrCreateVertexCollectionByName( + const std::string& name, bool waitForSync, VPackSlice options) { + std::shared_ptr def; + + def = getCollectionByName(ctx()->vocbase(), name); + if (def == nullptr) { + return createVertexCollection(name, waitForSync, options); + } + + return OperationResult(TRI_ERROR_NO_ERROR); +} + +bool GraphManager::renameGraphCollection(std::string oldName, std::string newName) { + // todo: return a result, by now just used in the graph modules + VPackBuilder graphsBuilder; + readGraphs(graphsBuilder, aql::PART_DEPENDENT); + VPackSlice graphs = graphsBuilder.slice(); + + SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection, + AccessMode::Type::WRITE); + + Result res = trx.begin(); + + if (!res.ok()) { + return false; + } + OperationOptions options; + OperationResult checkDoc; + + + for (auto graphSlice : VPackArrayIterator(graphs.get("graphs"))) { + VPackBuilder builder; + + graphSlice = graphSlice.resolveExternals(); + TRI_ASSERT(graphSlice.isObject() && graphSlice.hasKey(StaticStrings::KeyString)); + if (!graphSlice.isObject() || !graphSlice.hasKey(StaticStrings::KeyString)) { + // return {TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT}; + return false; + } + std::unique_ptr graph; + try { + graph = Graph::fromPersistence(graphSlice, _vocbase); + } catch (basics::Exception& e) { + // return {e.message(), e.code()}; + return false; + } + TRI_ASSERT(graph != nullptr); + if (graph == nullptr) { + return false; + } + + // rename not allowed in a smart collection + if (graph->isSmart()) { + continue; + } + + builder.openObject(); + builder.add(StaticStrings::KeyString, VPackValue(graphSlice.get(StaticStrings::KeyString).copyString())); + + builder.add(StaticStrings::GraphEdgeDefinitions, VPackValue(VPackValueType::Array)); + for (auto const& sGED : graph->edgeDefinitions()) { + builder.openObject(); + std::string col = sGED.first; + std::set froms = sGED.second.getFrom(); + std::set tos = sGED.second.getTo(); + + if (col != oldName) { + builder.add("collection", VPackValue(col)); + } else { + builder.add("collection", VPackValue(newName)); + } + + builder.add("from", VPackValue(VPackValueType::Array)); + for (auto const& from : froms) { + if (from != oldName) { + builder.add(VPackValue(from)); + } else { + builder.add(VPackValue(newName)); + } + } + builder.close(); // array + + builder.add("to", VPackValue(VPackValueType::Array)); + for (auto const& to : tos) { + if (to != oldName) { + builder.add(VPackValue(to)); + } else { + builder.add(VPackValue(newName)); + } + } + builder.close(); // array + + builder.close(); // object + } + builder.close(); // array + builder.close(); // object + + try { + checkDoc = + trx.update(StaticStrings::GraphCollection, builder.slice(), options); + if (checkDoc.fail()) { + trx.finish(checkDoc.result); + return false; + } + } catch (...) { + } + } + + trx.finish(checkDoc.result); + return true; +} + +Result GraphManager::checkForEdgeDefinitionConflicts( + std::map const& edgeDefinitions) + const { + VPackBuilder graphsBuilder; + // TODO Maybe use the cache here + readGraphs(graphsBuilder, aql::PART_DEPENDENT); + VPackSlice graphs = graphsBuilder.slice(); + + TRI_ASSERT(graphs.get("graphs").isArray()); + if (!graphs.get("graphs").isArray()) { + return {TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT}; + } + + for (auto graphSlice : VPackArrayIterator(graphs.get("graphs"))) { + graphSlice = graphSlice.resolveExternals(); + TRI_ASSERT(graphSlice.isObject() && graphSlice.hasKey("_key")); + if (!graphSlice.isObject() || !graphSlice.hasKey("_key")) { + return {TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT}; + } + std::unique_ptr graph; + try { + graph = Graph::fromPersistence(graphSlice, _vocbase); + } catch (basics::Exception& e) { + return {e.code(), e.message()}; + } + TRI_ASSERT(graph != nullptr); + if (graph == nullptr) { + return {TRI_ERROR_OUT_OF_MEMORY}; + } + + for (auto const& sGED : graph->edgeDefinitions()) { + std::string col = sGED.first; + + auto it = edgeDefinitions.find(col); + if (it != edgeDefinitions.end()) { + if (sGED.second != it->second) { + // found an incompatible edge definition for the same collection + return Result(TRI_ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS, + sGED.first + " " + + std::string{TRI_errno_string( + TRI_ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS)}); + } + } + } + } + + return {TRI_ERROR_NO_ERROR}; +} + +OperationResult GraphManager::findOrCreateCollectionsByEdgeDefinitions( + std::map const& edgeDefinitions, + bool waitForSync, VPackSlice options) { + for (auto const& it : edgeDefinitions) { + EdgeDefinition const& edgeDefinition = it.second; + OperationResult res = findOrCreateCollectionsByEdgeDefinition( + edgeDefinition, waitForSync, options); + + if (res.fail()) { + return res; + } + } + + return OperationResult{TRI_ERROR_NO_ERROR}; +} + +OperationResult GraphManager::findOrCreateCollectionsByEdgeDefinition( + EdgeDefinition const& edgeDefinition, bool waitForSync, + VPackSlice const options) { + std::string const& edgeCollection = edgeDefinition.getName(); + std::shared_ptr def = + getCollectionByName(ctx()->vocbase(), edgeCollection); + + if (def == nullptr) { + OperationResult res = + createEdgeCollection(edgeCollection, waitForSync, options); + if (res.fail()) { + return res; + } + } + + std::unordered_set vertexCollections; + + // duplicates in from and to shouldn't occur, but are safely ignored here + for (auto const& colName : edgeDefinition.getFrom()) { + vertexCollections.emplace(colName); + } + for (auto const& colName : edgeDefinition.getTo()) { + vertexCollections.emplace(colName); + } + for (auto const& colName : vertexCollections) { + def = getCollectionByName(ctx()->vocbase(), colName); + if (def == nullptr) { + OperationResult res = + createVertexCollection(colName, waitForSync, options); + if (res.fail()) { + return res; + } + } + } + + return OperationResult{TRI_ERROR_NO_ERROR}; +} + +/// @brief extract the collection by either id or name, may return nullptr! +std::shared_ptr GraphManager::getCollectionByName( + const TRI_vocbase_t& vocbase, std::string const& name) { + if (!name.empty()) { + // try looking up the collection by name then + try { + if (arangodb::ServerState::instance()->isRunningInCluster()) { + ClusterInfo* ci = ClusterInfo::instance(); + return ci->getCollection(vocbase.name(), name); + } else { + return vocbase.lookupCollection(name); + } + } catch (...) { + } + } + + return nullptr; +} + +bool GraphManager::graphExists(std::string const& graphName) const { + VPackBuilder checkDocument; + { + VPackObjectBuilder guard(&checkDocument); + checkDocument.add(StaticStrings::KeyString, VPackValue(graphName)); + } + + SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection, + AccessMode::Type::READ); + trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION); + + Result res = trx.begin(); + + if (!res.ok()) { + return false; + } + + OperationOptions options; + try { + OperationResult checkDoc = trx.document(StaticStrings::GraphCollection, + checkDocument.slice(), options); + if (checkDoc.fail()) { + trx.finish(checkDoc.result); + return false; + } + trx.finish(checkDoc.result); + } catch (...) { + } + return true; +} + +ResultT> GraphManager::lookupGraphByName( + std::string const& name) const { + SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection, AccessMode::Type::READ); + + Result res = trx.begin(); + + if (res.fail()) { + std::stringstream ss; + ss << "while looking up graph '" << name << "': " << res.errorMessage(); + res.reset(res.errorNumber(), ss.str()); + return {res}; + } + + VPackBuilder b; + { + VPackObjectBuilder guard(&b); + b.add(StaticStrings::KeyString, VPackValue(name)); + } + + // Default options are enough here + OperationOptions options; + + OperationResult result = trx.document(StaticStrings::GraphCollection, b.slice(), options); + + // Commit or abort. + res = trx.finish(result.result); + + if (result.fail()) { + if (result.errorNumber() == TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND) { + return {TRI_ERROR_GRAPH_NOT_FOUND}; + } else { + return Result{result.errorNumber(), "while looking up graph '" + name + "'"}; + } + } + + if (res.fail()) { + std::stringstream ss; + ss << "while looking up graph '" << name << "': " << res.errorMessage(); + res.reset(res.errorNumber(), ss.str()); + return {res}; + } + return {Graph::fromPersistence(result.slice(), _vocbase)}; +} + +OperationResult GraphManager::createGraph(VPackSlice document, + bool waitForSync) const { + VPackSlice graphNameSlice = document.get("name"); + if (!graphNameSlice.isString()) { + return OperationResult{TRI_ERROR_GRAPH_CREATE_MISSING_NAME}; + } + std::string const graphName = graphNameSlice.copyString(); + + if (graphExists(graphName)) { + return OperationResult{TRI_ERROR_GRAPH_DUPLICATE}; + } + + auto graphRes = buildGraphFromInput(graphName, document); + if (graphRes.fail()) { + return OperationResult{graphRes.copy_result()}; + } + // Guaranteed to not be nullptr + std::unique_ptr graph = std::move(graphRes.get()); + TRI_ASSERT(graph != nullptr); + + // check permissions + Result res = checkCreateGraphPermissions(graph.get()); + if (res.fail()) { + return OperationResult{res}; + } + + // check edgeDefinitionConflicts + res = checkForEdgeDefinitionConflicts(graph->edgeDefinitions()); + if (res.fail()) { + return OperationResult{res}; + } + + // Make sure all collections exist and are created + res = ensureCollections(graph.get(), waitForSync); + if (res.fail()) { + return OperationResult{res}; + } + + // finally save the graph + return storeGraph(*(graph.get()), waitForSync, false); +} + +OperationResult GraphManager::storeGraph(Graph const& graph, bool waitForSync, + bool isUpdate) const { + VPackBuilder builder; + builder.openObject(); + graph.toPersistence(builder); + builder.close(); + + // Here we need a second transaction. + // If now someone has created a graph with the same name + // in the meanwhile, sorry bad luck. + SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection, + AccessMode::Type::WRITE); + trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION); + + OperationOptions options; + options.waitForSync = waitForSync; + Result res = trx.begin(); + if (res.fail()) { + return OperationResult{std::move(res)}; + } + OperationResult result; + if (isUpdate) { + result = + trx.update(StaticStrings::GraphCollection, builder.slice(), options); + } else { + result = + trx.insert(StaticStrings::GraphCollection, builder.slice(), options); + } + if (!result.ok()) { + trx.finish(result.result); + return result; + } + res = trx.finish(result.result); + if (res.fail()) { + return OperationResult{std::move(res)}; + } + return result; +} + +Result GraphManager::ensureCollections(Graph const* graph, bool waitForSync) const { + // Validation Phase collect a list of collections to create + std::unordered_set documentCollectionsToCreate{}; + std::unordered_set edgeCollectionsToCreate{}; + + TRI_vocbase_t* vocbase = &(ctx()->vocbase()); + Result innerRes{TRI_ERROR_NO_ERROR}; + + // Check that all edgeCollections are either to be created + // or exist in a valid way. + for (auto const& edgeColl : graph->edgeCollections()) { + bool found = false; + Result res = methods::Collections::lookup( + vocbase, edgeColl, [&found, &innerRes, &graph](LogicalCollection& col) { + if (col.type() != TRI_COL_TYPE_EDGE) { + innerRes.reset( + TRI_ERROR_GRAPH_EDGE_DEFINITION_IS_DOCUMENT, + "Collection: '" + col.name() + "' is not an EdgeCollection"); + } else { + innerRes = graph->validateCollection(col); + found = true; + } + }); + if (innerRes.fail()) { + return innerRes; + } + // Check if we got an error other then CollectionNotFound + if (res.fail() && !res.is(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND)) { + return res; + } + if (!found) { + edgeCollectionsToCreate.emplace(edgeColl); + } + } + + // Check that all vertexCollections are either to be created + // or exist in a valid way. + // If there is an edge collection used as a vertex collection + // it will not be created twice + for (auto const& vertexColl : graph->vertexCollections()) { + bool found = false; + Result res = methods::Collections::lookup( + vocbase, vertexColl, + [&found, &innerRes, &graph](LogicalCollection& col) { + innerRes = graph->validateCollection(col); + found = true; + }); + if (innerRes.fail()) { + return innerRes; + } + // Check if we got an error other then CollectionNotFound + if (res.fail() && !res.is(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND)) { + return res; + } + + if (!found) { + if (edgeCollectionsToCreate.find(vertexColl) == edgeCollectionsToCreate.end()) { + documentCollectionsToCreate.emplace(vertexColl); + } + } + } + + + +#ifdef USE_ENTERPRISE + { + Result res = ensureSmartCollectionSharding(graph, waitForSync, documentCollectionsToCreate); + if (res.fail()) { + return res; + } + } +#endif + + VPackBuilder optionsBuilder; + optionsBuilder.openObject(); + graph->createCollectionOptions(optionsBuilder, waitForSync); + optionsBuilder.close(); + VPackSlice options = optionsBuilder.slice(); + + // Create Document Collections + for (auto const& vertexColl : documentCollectionsToCreate) { + Result res = methods::Collections::create( + vocbase, vertexColl, TRI_COL_TYPE_DOCUMENT, options, waitForSync, true, + [&](LogicalCollection& coll) {}); + if (res.fail()) { + return res; + } + } + + // Create Edge Collections + for (auto const& edgeColl : edgeCollectionsToCreate) { + Result res = methods::Collections::create(vocbase, edgeColl, TRI_COL_TYPE_EDGE, + options, waitForSync, true, + [&](LogicalCollection& coll) {}); + if (res.fail()) { + return res; + } + } + + return TRI_ERROR_NO_ERROR; +}; + +OperationResult GraphManager::readGraphs(velocypack::Builder& builder, + aql::QueryPart const queryPart) const { + std::string const queryStr{"FOR g IN _graphs RETURN g"}; + return readGraphByQuery(builder, queryPart, queryStr); +} + +OperationResult GraphManager::readGraphKeys(velocypack::Builder& builder, + aql::QueryPart const queryPart) const { + std::string const queryStr{"FOR g IN _graphs RETURN g._key"}; + return readGraphByQuery(builder, queryPart, queryStr); +} + +OperationResult GraphManager::readGraphByQuery(velocypack::Builder& builder, + aql::QueryPart const queryPart, std::string queryStr) const { + arangodb::aql::Query query(false, ctx()->vocbase(), + arangodb::aql::QueryString(queryStr), nullptr, + nullptr, queryPart); + + LOG_TOPIC(DEBUG, arangodb::Logger::FIXME) + << "starting to load graphs information"; + aql::QueryResult queryResult = + query.executeSync(QueryRegistryFeature::QUERY_REGISTRY); + + if (queryResult.code != TRI_ERROR_NO_ERROR) { + if (queryResult.code == TRI_ERROR_REQUEST_CANCELED || + (queryResult.code == TRI_ERROR_QUERY_KILLED)) { + return OperationResult(TRI_ERROR_REQUEST_CANCELED); + } + return OperationResult(queryResult.code); + } + + VPackSlice graphsSlice = queryResult.result->slice(); + + if (graphsSlice.isNone()) { + return OperationResult(TRI_ERROR_OUT_OF_MEMORY); + } else if (!graphsSlice.isArray()) { + LOG_TOPIC(ERR, arangodb::Logger::FIXME) + << "cannot read graphs from _graphs collection"; + } + + builder.add(VPackValue(VPackValueType::Object)); + builder.add("graphs", graphsSlice); + builder.close(); + + return OperationResult(TRI_ERROR_NO_ERROR); +} + +Result GraphManager::checkForEdgeDefinitionConflicts( + arangodb::graph::EdgeDefinition const& edgeDefinition) const { + std::map edgeDefs{ + std::make_pair(edgeDefinition.getName(), edgeDefinition)}; + + return checkForEdgeDefinitionConflicts(edgeDefs); +} + +Result GraphManager::checkCreateGraphPermissions( + Graph const* graph) const { + std::string const& databaseName = ctx()->vocbase().name(); + + std::stringstream stringstream; + stringstream << "When creating graph " << databaseName << "." << graph->name() << ": "; + std::string const logprefix = stringstream.str(); + + ExecContext const* execContext = ExecContext::CURRENT; + if (execContext == nullptr) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix + << "Permissions are turned off."; + return TRI_ERROR_NO_ERROR; + } + + // Test if we are allowed to modify _graphs first. + // Note that this check includes the following check in the loop + // if (!collectionExists(col) && !canUseDatabaseRW) + // as canUseDatabase(RW) <=> canUseCollection("_...", RW). + // However, in case a collection has to be created but can't, we have to throw + // FORBIDDEN instead of READ_ONLY for backwards compatibility. + if (!execContext->canUseDatabase(auth::Level::RW)) { + // Check for all collections: if it exists and if we have RO access to it. + // If none fails the check above we need to return READ_ONLY. + // Otherwise we return FORBIDDEN + auto checkCollectionAccess = [&](std::string const& col) -> bool { + // We need RO on all collections. And, in case any collection does not + // exist, we need RW on the database. + if (!collectionExists(col)) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "Cannot create collection " + << databaseName << "." << col; + return false; + } + if (!execContext->canUseCollection(col, auth::Level::RO)) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "No read access to " + << databaseName << "." << col; + return false; + } + return true; + }; + + // Test all edge Collections + for (auto const& it : graph->edgeCollections()) { + if (!checkCollectionAccess(it)) { + return {TRI_ERROR_FORBIDDEN, "Createing Graphs requires RW access on the database (" + databaseName + ")"}; + } + } + + // Test all vertex Collections + for (auto const& it : graph->vertexCollections()) { + if (!checkCollectionAccess(it)) { + return {TRI_ERROR_FORBIDDEN, "Createing Graphs requires RW access on the database (" + databaseName + ")"}; + } + } + + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "No write access to " + << databaseName << "." + << StaticStrings::GraphCollection; + return {TRI_ERROR_ARANGO_READ_ONLY, "Createing Graphs requires RW access on the database (" + databaseName + ")"}; + } + + auto checkCollectionAccess = [&](std::string const& col) -> bool { + // We need RO on all collections. And, in case any collection does not + // exist, we need RW on the database. + if (!execContext->canUseCollection(col, auth::Level::RO)) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "No read access to " + << databaseName << "." << col; + return false; + } + return true; + }; + + // Test all edge Collections + for (auto const& it : graph->edgeCollections()) { + if (!checkCollectionAccess(it)) { + return TRI_ERROR_FORBIDDEN; + } + } + + // Test all vertex Collections + for (auto const& it : graph->vertexCollections()) { + if (!checkCollectionAccess(it)) { + return TRI_ERROR_FORBIDDEN; + } + } + return TRI_ERROR_NO_ERROR; +} + +bool GraphManager::collectionExists(std::string const &collection) const { + return getCollectionByName(ctx()->vocbase(), collection) != nullptr; +} + +OperationResult GraphManager::removeGraph(Graph const& graph, bool waitForSync, + bool dropCollections) { + + std::unordered_set leadersToBeRemoved; + std::unordered_set followersToBeRemoved; + + if (dropCollections) { + auto addToRemoveCollections = [this, &graph, &leadersToBeRemoved, + &followersToBeRemoved]( + std::string const& colName) { + std::shared_ptr col = + getCollectionByName(ctx()->vocbase(), colName); + if (col == nullptr) { + return; + } + + if (col->distributeShardsLike().empty()) { + pushCollectionIfMayBeDropped(colName, graph.name(), leadersToBeRemoved); + } else { + pushCollectionIfMayBeDropped(colName, graph.name(), + followersToBeRemoved); + } + }; + + for (auto const& vertexCollection : graph.vertexCollections()) { + addToRemoveCollections(vertexCollection); + } + for (auto const& orphanCollection : graph.orphanCollections()) { + addToRemoveCollections(orphanCollection); + } + for (auto const& edgeCollection : graph.edgeCollections()) { + addToRemoveCollections(edgeCollection); + } + } + + Result permRes = checkDropGraphPermissions(graph, followersToBeRemoved, + leadersToBeRemoved); + if (permRes.fail()) { + return OperationResult{std::move(permRes)}; + } + + VPackBuilder builder; + { + VPackObjectBuilder guard(&builder); + builder.add(StaticStrings::KeyString, VPackValue(graph.name())); + } + + + { // Remove from _graphs + OperationOptions options; + options.waitForSync = waitForSync; + + Result res; + OperationResult result; + SingleCollectionTransaction trx{ctx(), StaticStrings::GraphCollection, + AccessMode::Type::WRITE}; + + res = trx.begin(); + if (res.fail()) { + return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND); + } + VPackSlice search = builder.slice(); + result = trx.remove(StaticStrings::GraphCollection, search, options); + + res = trx.finish(result.result); + if (result.fail()) { + return result; + } + if (res.fail()) { + return OperationResult{res}; + } + TRI_ASSERT(res.ok() && result.ok()); + } + + { // drop collections + + Result firstDropError; + // remove graph related collections. + // we are not able to do this in a transaction, so doing it afterwards. + // there may be no collections to drop when dropCollections is false. + TRI_ASSERT(dropCollections || + (leadersToBeRemoved.empty() && followersToBeRemoved.empty())); + // drop followers (with distributeShardsLike) first and leaders (which occur + // in some distributeShardsLike) second. + for (auto const& collection : + boost::join(followersToBeRemoved, leadersToBeRemoved)) { + Result dropResult; + Result found = methods::Collections::lookup( + &ctx()->vocbase(), collection, [&](LogicalCollection& coll) { + dropResult = methods::Collections::drop(&ctx()->vocbase(), &coll, + false, -1.0); + }); + if (dropResult.fail()) { + LOG_TOPIC(WARN, Logger::GRAPHS) + << "While removing graph `" << graph.name() << "`: " + << "Dropping collection `" << collection << "` failed with error " + << dropResult.errorNumber() << ": " << dropResult.errorMessage(); + // save the first error: + if (firstDropError.ok()) { + firstDropError = dropResult; + } + } + } + + if (firstDropError.fail()) { + return OperationResult{firstDropError}; + } + } + + return OperationResult{TRI_ERROR_NO_ERROR}; +} + +OperationResult GraphManager::pushCollectionIfMayBeDropped( + const std::string& colName, const std::string& graphName, + std::unordered_set& toBeRemoved) { + VPackBuilder graphsBuilder; + OperationResult result = readGraphs(graphsBuilder, aql::PART_DEPENDENT); + if (result.fail()) { + return result; + } + + VPackSlice graphs = graphsBuilder.slice(); + + bool collectionUnused = true; + TRI_ASSERT(graphs.get("graphs").isArray()); + + if (!graphs.get("graphs").isArray()) { + return OperationResult(TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT); + } + + for (auto graph : VPackArrayIterator(graphs.get("graphs"))) { + graph = graph.resolveExternals(); + if (!collectionUnused) { + // Short circuit + break; + } + if (graph.get(StaticStrings::KeyString).copyString() == graphName) { + continue; + } + + // check edge definitions + VPackSlice edgeDefinitions = graph.get(StaticStrings::GraphEdgeDefinitions); + if (edgeDefinitions.isArray()) { + for (auto const& edgeDefinition : VPackArrayIterator(edgeDefinitions)) { + // edge collection + std::string collection = + edgeDefinition.get("collection").copyString(); + if (collection == colName) { + collectionUnused = false; + } + // from's + if (::ArrayContainsCollection(edgeDefinition.get(StaticStrings::GraphFrom), colName)) { + collectionUnused = false; + break; + } + if (::ArrayContainsCollection(edgeDefinition.get(StaticStrings::GraphTo), colName)) { + collectionUnused = false; + break; + } + } + } else { + return OperationResult(TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT); + } + + // check orphan collections + VPackSlice orphanCollections = graph.get(StaticStrings::GraphOrphans); + if (orphanCollections.isArray()) { + if (::ArrayContainsCollection(orphanCollections, colName)) { + collectionUnused = false; + break; + } + } + } + + if (collectionUnused) { + toBeRemoved.emplace(colName); + } + + return OperationResult(TRI_ERROR_NO_ERROR); +} + +Result GraphManager::checkDropGraphPermissions( + const Graph& graph, + const std::unordered_set& followersToBeRemoved, + const std::unordered_set& leadersToBeRemoved) { + std::string const& databaseName = ctx()->vocbase().name(); + + std::stringstream stringstream; + stringstream << "When dropping graph " << databaseName << "." << graph.name() + << ": "; + std::string const logprefix = stringstream.str(); + + ExecContext const* execContext = ExecContext::CURRENT; + if (execContext == nullptr) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix + << "Permissions are turned off."; + return TRI_ERROR_NO_ERROR; + } + + bool mustDropAtLeastOneCollection = + !followersToBeRemoved.empty() || !leadersToBeRemoved.empty(); + bool canUseDatabaseRW = execContext->canUseDatabase(auth::Level::RW); + + if (mustDropAtLeastOneCollection && !canUseDatabaseRW) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) + << logprefix << "Must drop at least one collection in " << databaseName + << ", but don't have permissions."; + return TRI_ERROR_FORBIDDEN; + } + + for (auto const& col : boost::join(followersToBeRemoved, leadersToBeRemoved)) { + // We need RW to drop a collection. + if (!execContext->canUseCollection(col, auth::Level::RW)) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "No write access to " + << databaseName << "." << col; + return TRI_ERROR_FORBIDDEN; + } + } + + // We need RW on _graphs (which is the same as RW on the database). But in + // case we don't even have RO access, throw FORBIDDEN instead of READ_ONLY. + if (!execContext->canUseCollection(StaticStrings::GraphCollection, + auth::Level::RO)) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "No read access to " + << databaseName << "." + << StaticStrings::GraphCollection; + return TRI_ERROR_FORBIDDEN; + } + + // Note that this check includes the following check from before + // if (mustDropAtLeastOneCollection && !canUseDatabaseRW) + // as canUseDatabase(RW) <=> canUseCollection("_...", RW). + // However, in case a collection has to be created but can't, we have to throw + // FORBIDDEN instead of READ_ONLY for backwards compatibility. + if (!execContext->canUseCollection(StaticStrings::GraphCollection, + auth::Level::RW)) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "No write access to " + << databaseName << "." + << StaticStrings::GraphCollection; + return TRI_ERROR_ARANGO_READ_ONLY; + } + + return TRI_ERROR_NO_ERROR; +} + +ResultT> GraphManager::buildGraphFromInput(std::string const& graphName, VPackSlice input) const { + try { + TRI_ASSERT(input.isObject()); + return Graph::fromUserInput(graphName, input, input.get(StaticStrings::GraphOptions)); + } catch (arangodb::basics::Exception const& e) { + return Result{e.code(), e.message()}; + } catch (...) { + return {TRI_ERROR_INTERNAL}; + } + TRI_ASSERT(false); // Catch all above! + return {TRI_ERROR_INTERNAL }; +} diff --git a/arangod/Graph/GraphManager.h b/arangod/Graph/GraphManager.h new file mode 100644 index 0000000000..cbb9f25549 --- /dev/null +++ b/arangod/Graph/GraphManager.h @@ -0,0 +1,204 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Heiko Kernbach +//////////////////////////////////////////////////////////////////////////////// + +#ifndef ARANGOD_GRAPH_GRAPHMANAGER_H +#define ARANGOD_GRAPH_GRAPHMANAGER_H + +#include +#include +#include + +#include "Aql/Query.h" +#include "Aql/VariableGenerator.h" +#include "Basics/ReadWriteLock.h" +#include "Cluster/ClusterInfo.h" +#include "Cluster/ResultT.h" +#include "Graph/Graph.h" +#include "Transaction/Methods.h" +#include "Transaction/StandaloneContext.h" +#include "Utils/OperationResult.h" + +namespace arangodb { +namespace graph { + +class GraphManager { + + private: + + TRI_vocbase_t& _vocbase; + + bool _isInTransaction; + + std::shared_ptr ctx() const; + + //////////////////////////////////////////////////////////////////////////////// + /// @brief find or create vertex collection by name + //////////////////////////////////////////////////////////////////////////////// + OperationResult findOrCreateVertexCollectionByName(const std::string& name, + bool waitForSync, VPackSlice options); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief find or create collection by name and type + //////////////////////////////////////////////////////////////////////////////// + OperationResult createCollection(std::string const& name, TRI_col_type_e colType, + bool waitForSync, VPackSlice options); + + public: + explicit GraphManager(TRI_vocbase_t& vocbase) : GraphManager(vocbase, false) {}; + + GraphManager(TRI_vocbase_t& vocbase, bool isInTransaction) + : _vocbase(vocbase), _isInTransaction(isInTransaction) { + } + + OperationResult readGraphs(velocypack::Builder& builder, + arangodb::aql::QueryPart queryPart) const; + + OperationResult readGraphKeys(velocypack::Builder& builder, + arangodb::aql::QueryPart queryPart) const; + + OperationResult readGraphByQuery(velocypack::Builder& builder, + arangodb::aql::QueryPart queryPart, + std::string queryStr) const; + + //////////////////////////////////////////////////////////////////////////////// + /// @brief find and return a collections if available + //////////////////////////////////////////////////////////////////////////////// + static std::shared_ptr getCollectionByName( + const TRI_vocbase_t& vocbase, std::string const& name); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief checks wheter a graph exists or not + //////////////////////////////////////////////////////////////////////////////// + bool graphExists(std::string const& graphName) const; + + //////////////////////////////////////////////////////////////////////////////// + /// @brief lookup a graph by name + //////////////////////////////////////////////////////////////////////////////// + ResultT> lookupGraphByName(std::string const& name) const; + + //////////////////////////////////////////////////////////////////////////////// + /// @brief create a graph + //////////////////////////////////////////////////////////////////////////////// + OperationResult createGraph(VPackSlice document, bool waitForSync) const; + + //////////////////////////////////////////////////////////////////////////////// + /// @brief find or create collections by EdgeDefinitions + //////////////////////////////////////////////////////////////////////////////// + OperationResult findOrCreateCollectionsByEdgeDefinitions( + std::map const& edgeDefinitions, + bool waitForSync, VPackSlice options); + + OperationResult findOrCreateCollectionsByEdgeDefinition( + EdgeDefinition const& edgeDefinition, bool waitForSync, + VPackSlice options); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief create a vertex collection + //////////////////////////////////////////////////////////////////////////////// + OperationResult createVertexCollection(std::string const& name, + bool waitForSync, VPackSlice options); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief create an edge collection + //////////////////////////////////////////////////////////////////////////////// + OperationResult createEdgeCollection(std::string const& name, + bool waitForSync, VPackSlice options); + + /// @brief rename a collection used in an edge definition + bool renameGraphCollection(std::string oldName, std::string newName); + + /// @brief check if the edge definitions conflicts with one in an existing + /// graph + Result checkForEdgeDefinitionConflicts( + std::map const& + edgeDefinitions) const; + + /// @brief check if the edge definition conflicts with one in an existing + /// graph + Result checkForEdgeDefinitionConflicts( + arangodb::graph::EdgeDefinition const& edgeDefinition) const; + + /// @brief Remove a graph and optional all connected collections + OperationResult removeGraph(Graph const& graph, bool waitForSync, + bool dropCollections); + + OperationResult pushCollectionIfMayBeDropped( + const std::string& colName, const std::string& graphName, + std::unordered_set& toBeRemoved); + + bool collectionExists(std::string const& collection) const; + + + /** + * @brief Helper function to make sure all collections required + * for this graph are created or exist. + * Will fail if the collections cannot be created, or + * if they have a non-compatible sharding in SmartGraph-case. + * + * @param graph The graph information used to make sure all collections exist + * + * @return Either OK or an error. + */ + Result ensureCollections(Graph const* graph, bool waitForSync) const; + + + /** + * @brief Store the given graph + * + * @param graph The graph to store + * @param waitForSync Wait for Collection to sync + * @param isUpdate If it is an update on existing graph or a new one. + * + * @return The result of the insrt transaction or Error. + */ + OperationResult storeGraph(Graph const& graph, bool waitForSync, bool isUpdate) const; + + private: + +#ifdef USE_ENTERPRISE + Result ensureSmartCollectionSharding(Graph const* graph, bool waitForSync, std::unordered_set& documentCollections) const; +#endif + + /** + * @brief Create a new in memory graph object from the given input. + * This graph object does not create collections and does + * not check them. It cannot be used to access any kind + * of data. In order to get a "usable" Graph object use + * lookup by name. + * + * @param input The slice containing the graph data. + * + * @return A temporary Graph object + */ + ResultT> buildGraphFromInput(std::string const& graphName, arangodb::velocypack::Slice input) const; + + Result checkCreateGraphPermissions(Graph const* graph) const; + + Result checkDropGraphPermissions( + Graph const& graph, + std::unordered_set const& followersToBeRemoved, + std::unordered_set const& leadersToBeRemoved); +}; +} // namespace graph +} // namespace arangodb + +#endif // ARANGOD_GRAPH_GRAPHMANAGER_H diff --git a/arangod/Graph/GraphOperations.cpp b/arangod/Graph/GraphOperations.cpp new file mode 100644 index 0000000000..7c67ea3261 --- /dev/null +++ b/arangod/Graph/GraphOperations.cpp @@ -0,0 +1,1042 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Tobias Gödderz & Heiko Kernbach +//////////////////////////////////////////////////////////////////////////////// + +#include "GraphOperations.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Aql/Query.h" +#include "Basics/StaticStrings.h" +#include "Basics/VelocyPackHelper.h" +#include "Graph/Graph.h" +#include "Graph/GraphManager.h" +#include "RestServer/QueryRegistryFeature.h" +#include "Transaction/Methods.h" +#include "Transaction/SmartContext.h" +#include "Utils/ExecContext.h" +#include "Utils/OperationOptions.h" +#include "Utils/SingleCollectionTransaction.h" +#include "VocBase/LogicalCollection.h" +#include "VocBase/Methods/Collections.h" + +using namespace arangodb; +using namespace arangodb::graph; +using UserTransaction = transaction::Methods; + +std::shared_ptr GraphOperations::ctx() const { + return transaction::SmartContext::Create(_vocbase); +}; + +OperationResult GraphOperations::changeEdgeDefinitionForGraph( + const Graph& graph, const EdgeDefinition& newEdgeDef, bool waitForSync, + transaction::Methods& trx) { + std::string const& edgeDefinitionName = newEdgeDef.getName(); + + auto maybeOldEdgeDef = graph.getEdgeDefinition(edgeDefinitionName); + if (!maybeOldEdgeDef) { + // Graph doesn't contain this edge definition, no need to do anything. + return OperationResult{}; + } + EdgeDefinition const& oldEdgeDef = maybeOldEdgeDef.get(); + + // replace edgeDefinition + VPackBuilder builder; + builder.openObject(); + // build edge definitions start + builder.add(StaticStrings::KeyString, VPackValue(graph.name())); + builder.add(StaticStrings::GraphEdgeDefinitions, + VPackValue(VPackValueType::Array)); + + // now we have to build a new VPackObject for updating the + // edgeDefinition + for (auto const &it : graph.edgeDefinitions()) { + auto const& edgeDef = it.second; + + if (edgeDef.getName() == newEdgeDef.getName()) { + newEdgeDef.addToBuilder(builder); + } else { + edgeDef.addToBuilder(builder); + } + } + builder.close(); // array 'edgeDefinitions' + + GraphManager gmngr{_vocbase}; + + { // add orphans: + + // previous orphans may still be orphans... + std::set orphans{graph.orphanCollections()}; + + // previous vertex collections from the overwritten may be orphaned... + setUnion(orphans, oldEdgeDef.getFrom()); + setUnion(orphans, oldEdgeDef.getTo()); + + // ...except they occur in any other edge definition, including the new one. + for (auto const &it : graph.edgeDefinitions()) { + std::string const &edgeCollection = it.first; + + EdgeDefinition const &edgeDef = + edgeCollection == newEdgeDef.getName() ? newEdgeDef : it.second; + + setMinus(orphans, edgeDef.getFrom()); + setMinus(orphans, edgeDef.getTo()); + } + + builder.add(StaticStrings::GraphOrphans, VPackValue(VPackValueType::Array)); + for (auto const &orphan : orphans) { + builder.add(VPackValue(orphan)); + } + builder.close(); // array 'orphanCollections' + } + + builder.close(); // object (the graph) + + std::set newCollections; + + // add collections that didn't exist in the graph before to newCollections: + for (auto const &it : boost::join(newEdgeDef.getFrom(), newEdgeDef.getTo())) { + if (!graph.hasVertexCollection(it) && !graph.hasOrphanCollection(it)) { + newCollections.emplace(it); + } + } + + VPackBuilder collectionOptions; + collectionOptions.openObject(); + _graph.createCollectionOptions(collectionOptions, waitForSync); + collectionOptions.close(); + for (auto const& newCollection : newCollections) { + // While the collection is new in the graph, it may still already exist. + if (GraphManager::getCollectionByName(_vocbase, newCollection)) { + continue; + } + + OperationResult result = gmngr.createVertexCollection( + newCollection, waitForSync, collectionOptions.slice()); + if (result.fail()) { + return result; + } + } + + OperationOptions options; + options.waitForSync = waitForSync; + // now write to database + return trx.update(StaticStrings::GraphCollection, builder.slice(), options); +} + +OperationResult GraphOperations::eraseEdgeDefinition( + bool waitForSync, std::string edgeDefinitionName, bool dropCollection) { + // check if edgeCollection is available + OperationResult result = checkEdgeCollectionAvailability(edgeDefinitionName); + if (result.fail()) { + return result; + } + + if (dropCollection && !hasRWPermissionsFor(edgeDefinitionName)) { + return OperationResult{TRI_ERROR_FORBIDDEN}; + } + + std::unordered_set possibleOrphans; + std::unordered_set usedVertexCollections; + std::map edgeDefs = + _graph.edgeDefinitions(); + + VPackBuilder newEdgeDefs; + newEdgeDefs.add(VPackValue(VPackValueType::Array)); + + for (auto const& edgeDefinition : edgeDefs) { + if (edgeDefinition.second.getName() == edgeDefinitionName) { + for (auto const& from : edgeDefinition.second.getFrom()) { + possibleOrphans.emplace(from); + } + for (auto const& to : edgeDefinition.second.getTo()) { + possibleOrphans.emplace(to); + } + } else { + for (auto const& from : edgeDefinition.second.getFrom()) { + usedVertexCollections.emplace(from); + } + for (auto const& to : edgeDefinition.second.getTo()) { + usedVertexCollections.emplace(to); + } + + // add still existing edgeDefinition to builder for update commit + newEdgeDefs.openObject(); + newEdgeDefs.add("collection", + VPackValue(edgeDefinition.second.getName())); + newEdgeDefs.add("from", VPackValue(VPackValueType::Array)); + for (auto const& from : edgeDefinition.second.getFrom()) { + newEdgeDefs.add(VPackValue(from)); + } + newEdgeDefs.close(); // array + newEdgeDefs.add("to", VPackValue(VPackValueType::Array)); + for (auto const& to : edgeDefinition.second.getTo()) { + newEdgeDefs.add(VPackValue(to)); + } + newEdgeDefs.close(); // array + newEdgeDefs.close(); // object + } + } + + newEdgeDefs.close(); // array + + // build orphan array + VPackBuilder newOrphColls; + newOrphColls.add(VPackValue(VPackValueType::Array)); + for (auto const& orph : _graph.orphanCollections()) { + newOrphColls.add(VPackValue(orph)); + } + + for (auto const& po : possibleOrphans) { + if (usedVertexCollections.find(po) == usedVertexCollections.end()) { + newOrphColls.add(VPackValue(po)); + } + } + newOrphColls.close(); // array + + // remove edgeDefinition from graph config + + OperationOptions options; + options.waitForSync = waitForSync; + + VPackBuilder builder; + builder.openObject(); + builder.add(StaticStrings::KeyString, VPackValue(_graph.name())); + builder.add(StaticStrings::GraphEdgeDefinitions, newEdgeDefs.slice()); + builder.add(StaticStrings::GraphOrphans, newOrphColls.slice()); + builder.close(); + + SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection, + AccessMode::Type::WRITE); + trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION); + + Result res = trx.begin(); + + if (!res.ok()) { + res = trx.finish(res); + return OperationResult(res); + } + result = trx.update(StaticStrings::GraphCollection, builder.slice(), options); + + if (dropCollection) { + std::unordered_set collectionsToBeRemoved; + GraphManager gmngr{_vocbase}; + + // add the edge collection itself for removal + gmngr.pushCollectionIfMayBeDropped(edgeDefinitionName, _graph.name(), + collectionsToBeRemoved); + for (auto const& collection : collectionsToBeRemoved) { + Result resIn; + Result found = methods::Collections::lookup( + &_vocbase, collection, [&](LogicalCollection& coll) { + resIn = methods::Collections::drop(&_vocbase, &coll, false, + -1.0); + }); + + if (found.fail()) { + res = trx.finish(result.result); + return OperationResult(res); + } else if (resIn.fail()) { + res = trx.finish(result.result); + return OperationResult(res); + } + } + } + + res = trx.finish(result.result); + + if (result.ok() && res.fail()) { + return OperationResult(res); + } + return result; +} + +OperationResult GraphOperations::checkEdgeCollectionAvailability( + std::string edgeCollectionName) { + bool found = _graph.edgeCollections().find(edgeCollectionName) != + _graph.edgeCollections().end(); + + if (!found) { + return OperationResult(TRI_ERROR_GRAPH_EDGE_COLLECTION_NOT_USED); + } + + return OperationResult(TRI_ERROR_NO_ERROR); +} + +OperationResult GraphOperations::checkVertexCollectionAvailability( + std::string vertexCollectionName) { + std::shared_ptr def = + GraphManager::getCollectionByName(_vocbase, vertexCollectionName); + + if (def == nullptr) { + return OperationResult(Result( + TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST, + vertexCollectionName + " " + + std::string{ + TRI_errno_string(TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST)})); + } + + return OperationResult(TRI_ERROR_NO_ERROR); +} + +OperationResult GraphOperations::editEdgeDefinition( + VPackSlice edgeDefinitionSlice, bool waitForSync, + const std::string& edgeDefinitionName) { + auto maybeEdgeDef = EdgeDefinition::createFromVelocypack(edgeDefinitionSlice); + if (!maybeEdgeDef) { + return OperationResult{maybeEdgeDef}; + } + EdgeDefinition const& edgeDefinition = maybeEdgeDef.get(); + + // check if edgeCollection is available + OperationResult result = checkEdgeCollectionAvailability(edgeDefinitionName); + if (result.fail()) { + return result; + } + + Result permRes = checkEdgeDefinitionPermissions(edgeDefinition); + if (permRes.fail()) { + return OperationResult{permRes}; + } + + GraphManager gmngr{_vocbase}; + VPackBuilder collectionsOptions; + collectionsOptions.openObject(); + _graph.createCollectionOptions(collectionsOptions, waitForSync); + collectionsOptions.close(); + result = gmngr.findOrCreateCollectionsByEdgeDefinition( + edgeDefinition, waitForSync, collectionsOptions.slice()); + if (result.fail()) { + return result; + } + + if (!_graph.hasEdgeCollection(edgeDefinition.getName())) { + return OperationResult(TRI_ERROR_GRAPH_EDGE_COLLECTION_NOT_USED); + } + + // change definition for ALL graphs + VPackBuilder graphsBuilder; + gmngr.readGraphs(graphsBuilder, arangodb::aql::PART_DEPENDENT); + VPackSlice graphs = graphsBuilder.slice(); + + if (!graphs.get("graphs").isArray()) { + return OperationResult{TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT}; + } + + SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection, + AccessMode::Type::WRITE); + + Result res = trx.begin(); + + if (!res.ok()) { + return OperationResult(res); + } + + for (auto singleGraph : VPackArrayIterator(graphs.get("graphs"))) { + std::unique_ptr graph = Graph::fromPersistence(singleGraph.resolveExternals(), _vocbase); + result = + changeEdgeDefinitionForGraph(*(graph.get()), edgeDefinition, waitForSync, trx); + } + if (result.fail()) { + return result; + } + + res = trx.finish(TRI_ERROR_NO_ERROR); + if (result.ok() && res.fail()) { + return OperationResult(res); + } + return result; +}; + +OperationResult GraphOperations::addOrphanCollection(VPackSlice document, + bool waitForSync, + bool createCollection) { + GraphManager gmngr{_vocbase}; + std::string collectionName = document.get("collection").copyString(); + std::shared_ptr def; + + OperationResult result; + VPackBuilder collectionsOptions; + collectionsOptions.openObject(); + _graph.createCollectionOptions(collectionsOptions, waitForSync); + collectionsOptions.close(); + + if (_graph.hasVertexCollection(collectionName)) { + if (_graph.hasOrphanCollection(collectionName)) { + return OperationResult(TRI_ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS); + } + return OperationResult( + Result(TRI_ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF, + collectionName + " " + + std::string{TRI_errno_string( + TRI_ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF)})); + } + + def = GraphManager::getCollectionByName(_vocbase, collectionName); + if (def == nullptr) { + if (createCollection) { + result = gmngr.createVertexCollection(collectionName, waitForSync, + collectionsOptions.slice()); + if (result.fail()) { + return result; + } + } else { + return OperationResult( + Result(TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST, + collectionName + " " + + std::string{TRI_errno_string( + TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST)})); + } + } else { + if (def->type() != TRI_COL_TYPE_DOCUMENT) { + return OperationResult(TRI_ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX); + } + auto res = _graph.validateCollection(*(def.get())); + if (res.fail()) { + return OperationResult{std::move(res)}; + } + } + + VPackBuilder builder; + builder.openObject(); + builder.add(StaticStrings::KeyString, VPackValue(_graph.name())); + builder.add(StaticStrings::GraphOrphans, VPackValue(VPackValueType::Array)); + for (auto const& orph : _graph.orphanCollections()) { + if (orph != collectionName) { + builder.add(VPackValue(orph)); + } + } + builder.add(VPackValue(collectionName)); + builder.close(); // array + builder.close(); // object + + SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection, + AccessMode::Type::WRITE); + + Result res = trx.begin(); + + if (!res.ok()) { + trx.finish(TRI_ERROR_NO_ERROR); + return OperationResult(res); + } + + OperationOptions options; + options.waitForSync = waitForSync; + result = trx.update(StaticStrings::GraphCollection, builder.slice(), options); + + res = trx.finish(result.result); + if (result.ok() && res.fail()) { + return OperationResult(res); + } + return result; +} + +OperationResult GraphOperations::eraseOrphanCollection( + bool waitForSync, std::string collectionName, bool dropCollection) { + // check if collection exists within the orphan collections + std::set orphanCollections = + _graph.orphanCollections(); + + bool found = false; + for (auto const& oName : _graph.orphanCollections()) { + if (oName == collectionName) { + found = true; + } + } + if (!found) { + return OperationResult(TRI_ERROR_GRAPH_NOT_IN_ORPHAN_COLLECTION); + } + + // check if collection exists in the database + OperationResult result = checkVertexCollectionAvailability(collectionName); + if (result.fail()) { + return result; + } + + if (!hasRWPermissionsFor(collectionName)) { + return OperationResult{TRI_ERROR_FORBIDDEN}; + } + + VPackBuilder builder; + builder.openObject(); + builder.add(StaticStrings::KeyString, VPackValue(_graph.name())); + builder.add(StaticStrings::GraphOrphans, VPackValue(VPackValueType::Array)); + for (auto const& orph : _graph.orphanCollections()) { + if (orph != collectionName) { + builder.add(VPackValue(orph)); + } + } + builder.close(); // array + builder.close(); // object + + SingleCollectionTransaction trx(ctx(), StaticStrings::GraphCollection, + AccessMode::Type::WRITE); + trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION); + + Result res = trx.begin(); + + if (!res.ok()) { + return OperationResult(res); + } + OperationOptions options; + options.waitForSync = waitForSync; + + result = trx.update(StaticStrings::GraphCollection, builder.slice(), options); + res = trx.finish(result.result); + + if (dropCollection) { + std::unordered_set collectionsToBeRemoved; + GraphManager gmngr{_vocbase}; + gmngr.pushCollectionIfMayBeDropped(collectionName, "", collectionsToBeRemoved); + + for (auto const& collection : collectionsToBeRemoved) { + Result resIn; + Result found = methods::Collections::lookup( + &_vocbase, collection, [&](LogicalCollection& coll) { + resIn = methods::Collections::drop(&_vocbase, &coll, false, + -1.0); + }); + + if (found.fail()) { + return OperationResult(res); + } else if (resIn.fail()) { + return OperationResult(res); + } + } + } + + if (result.ok() && res.fail()) { + return OperationResult(res); + } + return result; +} + +OperationResult GraphOperations::addEdgeDefinition( + VPackSlice edgeDefinitionSlice, bool waitForSync) { + ResultT defRes = _graph.addEdgeDefinition(edgeDefinitionSlice); + if (defRes.fail()) { + return OperationResult{std::move(defRes.copy_result())}; + } + // Guaranteed to be non nullptr + TRI_ASSERT(defRes.get() != nullptr); + + // ... in different graph + GraphManager gmngr{_vocbase}; + + OperationResult result{gmngr.checkForEdgeDefinitionConflicts(*(defRes.get()))}; + if (result.fail()) { + // If this fails we will not persists. + return result; + } + + Result res = gmngr.ensureCollections(&_graph, waitForSync); + + if (res.fail()) { + return OperationResult{std::move(res)}; + } + + // finally save the graph + return gmngr.storeGraph(_graph, waitForSync, true); +}; + +// vertices + +// TODO check if collection is a vertex collection in _graph? +// TODO are orphans allowed? +OperationResult GraphOperations::getVertex(std::string const& collectionName, + std::string const& key, + boost::optional rev) { + return getDocument(collectionName, key, std::move(rev)); +}; + +// TODO check if definitionName is an edge collection in _graph? +OperationResult GraphOperations::getEdge(const std::string& definitionName, + const std::string& key, + boost::optional rev) { + return getDocument(definitionName, key, std::move(rev)); +} + +OperationResult GraphOperations::getDocument( + std::string const& collectionName, std::string const& key, + boost::optional rev) { + OperationOptions options; + options.ignoreRevs = !rev.is_initialized(); + + VPackBufferPtr searchBuffer = _getSearchSlice(key, rev); + VPackSlice search{searchBuffer->data()}; + + // find and load collection given by name or identifier + SingleCollectionTransaction trx(ctx(), collectionName, + AccessMode::Type::READ); + trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION); + + Result res = trx.begin(); + + if (!res.ok()) { + return OperationResult(res); + } + + OperationResult result = trx.document(collectionName, search, options); + + res = trx.finish(result.result); + + if (result.ok() && res.fail()) { + return OperationResult(res); + } + return result; +} + +GraphOperations::VPackBufferPtr GraphOperations::_getSearchSlice( + const std::string& key, boost::optional& rev) const { + VPackBuilder builder; + { + VPackObjectBuilder guard(&builder); + builder.add(StaticStrings::KeyString, VPackValue(key)); + if (rev) { + builder.add(StaticStrings::RevString, + VPackValue(TRI_RidToString(rev.get()))); + } + } + + return builder.buffer(); +} + +OperationResult GraphOperations::removeEdge(const std::string& definitionName, + const std::string& key, + boost::optional rev, + bool waitForSync, bool returnOld) { + OperationOptions options; + options.waitForSync = waitForSync; + options.returnOld = returnOld; + options.ignoreRevs = !rev.is_initialized(); + + VPackBufferPtr searchBuffer = _getSearchSlice(key, rev); + VPackSlice search{searchBuffer->data()}; + + SingleCollectionTransaction trx{ctx(), definitionName, + AccessMode::Type::WRITE}; + trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION); + + Result res = trx.begin(); + + if (!res.ok()) { + return OperationResult(res); + } + + OperationResult result = trx.remove(definitionName, search, options); + + res = trx.finish(result.result); + + if (result.ok() && res.fail()) { + return OperationResult(res); + } + return result; +} + +OperationResult GraphOperations::modifyDocument( + std::string const& collectionName, std::string const& key, + VPackSlice document, bool isPatch, boost::optional rev, + bool waitForSync, bool returnOld, bool returnNew, bool keepNull) { + OperationOptions options; + options.ignoreRevs = !rev.is_initialized(); + options.waitForSync = waitForSync; + options.returnNew = returnNew; + options.returnOld = returnOld; + if (isPatch) { + options.keepNull = keepNull; + // options.mergeObjects = true; + } + + // extract the revision, if single document variant and header given: + std::unique_ptr builder; + + VPackSlice keyInBody = document.get(StaticStrings::KeyString); + if ((rev && TRI_ExtractRevisionId(document) != rev.get()) || + keyInBody.isNone() || keyInBody.isNull() || + (keyInBody.isString() && keyInBody.copyString() != key)) { + // We need to rewrite the document with the given revision and key: + builder = std::make_unique(); + { + VPackObjectBuilder guard(builder.get()); + TRI_SanitizeObject(document, *builder); + builder->add(StaticStrings::KeyString, VPackValue(key)); + if (rev) { + builder->add(StaticStrings::RevString, + VPackValue(TRI_RidToString(rev.get()))); + } + } + document = builder->slice(); + } + + // find and load collection given by name or identifier + SingleCollectionTransaction trx(ctx(), collectionName, + AccessMode::Type::WRITE); + trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION); + + Result res = trx.begin(); + + if (!res.ok()) { + return OperationResult(res); + } + + OperationResult result; + + if (isPatch) { + result = trx.update(collectionName, document, options); + } else { + result = trx.replace(collectionName, document, options); + } + + res = trx.finish(result.result); + + if (result.ok() && res.fail()) { + return OperationResult(res); + } + return result; +} + +OperationResult GraphOperations::createDocument( + transaction::Methods* trx, std::string const& collectionName, + VPackSlice document, bool waitForSync, bool returnNew) { + OperationOptions options; + options.waitForSync = waitForSync; + options.returnNew = returnNew; + + OperationResult result; + result = trx->insert(collectionName, document, options); + + if (!result.ok()) { + trx->finish(result.result); + return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND); + } + Result res = trx->finish(result.result); + + if (result.ok() && res.fail()) { + return OperationResult(res); + } + return result; +} + +OperationResult GraphOperations::updateEdge(const std::string& definitionName, + const std::string& key, + VPackSlice document, + boost::optional rev, + bool waitForSync, bool returnOld, + bool returnNew, bool keepNull) { + return modifyDocument(definitionName, key, document, true, std::move(rev), + waitForSync, returnOld, returnNew, keepNull); +} + +OperationResult GraphOperations::replaceEdge(const std::string& definitionName, + const std::string& key, + VPackSlice document, + boost::optional rev, + bool waitForSync, bool returnOld, + bool returnNew, bool keepNull) { + return modifyDocument(definitionName, key, document, false, std::move(rev), + waitForSync, returnOld, returnNew, keepNull); +} + +OperationResult GraphOperations::createEdge(const std::string& definitionName, + VPackSlice document, + bool waitForSync, bool returnNew) { + VPackSlice fromStringSlice = document.get(StaticStrings::FromString); + VPackSlice toStringSlice = document.get(StaticStrings::ToString); + + if (fromStringSlice.isNone() || toStringSlice.isNone()) { + return OperationResult(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE); + } + std::string fromString = fromStringSlice.copyString(); + std::string toString = toStringSlice.copyString(); + + size_t pos = fromString.find('/'); + std::string fromCollectionName; + std::string fromCollectionKey; + if (pos != std::string::npos) { + fromCollectionName = fromString.substr(0, pos); + fromCollectionKey = fromString.substr(pos + 1, fromString.length()); + } else { + return OperationResult(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE); + } + + pos = toString.find('/'); + std::string toCollectionName; + std::string toCollectionKey; + if (pos != std::string::npos) { + toCollectionName = toString.substr(0, pos); + toCollectionKey = toString.substr(pos + 1, toString.length()); + } else { + return OperationResult(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE); + } + + // check if vertex collections are part of the graph definition + auto it = _graph.vertexCollections().find(fromCollectionName); + if (it == _graph.vertexCollections().end()) { + // not found from vertex + return OperationResult(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND); + } + it = _graph.vertexCollections().find(toCollectionName); + if (it == _graph.vertexCollections().end()) { + // not found to vertex + return OperationResult(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND); + } + + OperationOptions options; + + VPackBuilder bT; + { + VPackObjectBuilder guard(&bT); + bT.add(StaticStrings::KeyString, VPackValue(toCollectionKey)); + } + + VPackBuilder bF; + { + VPackObjectBuilder guard(&bF); + bF.add(StaticStrings::KeyString, VPackValue(fromCollectionKey)); + } + + std::vector readCollections; + std::vector writeCollections; + readCollections.emplace_back(fromCollectionName); + readCollections.emplace_back(toCollectionName); + writeCollections.emplace_back(definitionName); + + transaction::Options trxOptions; + trxOptions.waitForSync = waitForSync; + + std::unique_ptr trx(new UserTransaction( + ctx(), readCollections, writeCollections, {}, trxOptions)); + + Result res = trx->begin(); + if (!res.ok()) { + return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND); + } + OperationResult resultFrom = + trx->document(fromCollectionName, bF.slice(), options); + OperationResult resultTo = + trx->document(toCollectionName, bT.slice(), options); + + if (!resultFrom.ok()) { + trx->finish(resultFrom.result); + return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND); + } + if (!resultTo.ok()) { + trx->finish(resultTo.result); + return OperationResult(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND); + } + return createDocument(trx.get(), definitionName, document, waitForSync, + returnNew); +} + +OperationResult GraphOperations::updateVertex( + const std::string& collectionName, const std::string& key, + VPackSlice document, boost::optional rev, bool waitForSync, + bool returnOld, bool returnNew, bool keepNull) { + return modifyDocument(collectionName, key, document, true, std::move(rev), + waitForSync, returnOld, returnNew, keepNull); +} + +OperationResult GraphOperations::replaceVertex( + const std::string& collectionName, const std::string& key, + VPackSlice document, boost::optional rev, bool waitForSync, + bool returnOld, bool returnNew, bool keepNull) { + return modifyDocument(collectionName, key, document, false, std::move(rev), + waitForSync, returnOld, returnNew, keepNull); +} + +OperationResult GraphOperations::createVertex(const std::string& collectionName, + VPackSlice document, + bool waitForSync, + bool returnNew) { + transaction::Options trxOptions; + + std::vector writeCollections; + writeCollections.emplace_back(collectionName); + std::unique_ptr trx( + new UserTransaction(ctx(), {}, writeCollections, {}, trxOptions)); + + Result res = trx->begin(); + + if (!res.ok()) { + return OperationResult(res); + } + + return createDocument(trx.get(), collectionName, document, waitForSync, + returnNew); +} + +OperationResult GraphOperations::removeVertex( + const std::string& collectionName, const std::string& key, + boost::optional rev, bool waitForSync, bool returnOld) { + OperationOptions options; + options.waitForSync = waitForSync; + options.returnOld = returnOld; + options.ignoreRevs = !rev.is_initialized(); + + VPackBufferPtr searchBuffer = _getSearchSlice(key, rev); + VPackSlice search{searchBuffer->data()}; + + auto const& edgeCollections = _graph.edgeCollections(); + std::vector trxCollections; + + trxCollections.emplace_back(collectionName); + + for (auto const& it : edgeCollections) { + trxCollections.emplace_back(it); + } + + transaction::Options trxOptions; + trxOptions.waitForSync = waitForSync; + auto context = ctx(); + UserTransaction trx{context, {}, trxCollections, {}, trxOptions}; + + Result res = trx.begin(); + + if (!res.ok()) { + return OperationResult(res); + } + + OperationResult result = trx.remove(collectionName, search, options); + + { + aql::QueryString const queryString = aql::QueryString{ + "FOR e IN @@collection " + "FILTER e._from == @vertexId " + "OR e._to == @vertexId " + "REMOVE e IN @@collection"}; + + std::string const vertexId = collectionName + "/" + key; + + for (auto const& edgeCollection : edgeCollections) { + std::shared_ptr bindVars{std::make_shared()}; + + bindVars->add(VPackValue(VPackValueType::Object)); + bindVars->add("@collection", VPackValue(edgeCollection)); + bindVars->add("vertexId", VPackValue(vertexId)); + bindVars->close(); + + arangodb::aql::Query query(false, _vocbase, queryString, bindVars, + nullptr, arangodb::aql::PART_DEPENDENT); + query.setTransactionContext(context); + + auto queryResult = query.executeSync(QueryRegistryFeature::QUERY_REGISTRY); + + if (queryResult.code != TRI_ERROR_NO_ERROR) { + return OperationResult(queryResult.code); + } + } + } + + res = trx.finish(result.result); + + if (result.ok() && res.fail()) { + return OperationResult(res); + } + return result; +} + +bool GraphOperations::collectionExists(std::string const& collection) const { + GraphManager gmngr{_vocbase}; + return gmngr.collectionExists(collection); +} + +bool GraphOperations::hasROPermissionsFor(std::string const& collection) const { + return hasPermissionsFor(collection, auth::Level::RO); +} + +bool GraphOperations::hasRWPermissionsFor(std::string const& collection) const { + return hasPermissionsFor(collection, auth::Level::RW); +} + +bool GraphOperations::hasPermissionsFor(std::string const& collection, + auth::Level level) const { + std::string const& databaseName = _vocbase.name(); + + std::stringstream stringstream; + stringstream << "When checking " << convertFromAuthLevel(level) + << " permissions for " << databaseName << "." << collection + << ": "; + std::string const logprefix = stringstream.str(); + + ExecContext const* execContext = ExecContext::CURRENT; + if (execContext == nullptr) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix + << "Permissions are turned off."; + return true; + } + + if (execContext->canUseCollection(collection, level)) { + return true; + } + + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "Not allowed."; + return false; +} + +Result GraphOperations::checkEdgeDefinitionPermissions( + EdgeDefinition const& edgeDefinition) const { + std::string const& databaseName = _vocbase.name(); + + std::stringstream stringstream; + stringstream << "When checking permissions for edge definition `" + << edgeDefinition.getName() << "` of graph `" << databaseName + << "." << graph().name() << "`: "; + std::string const logprefix = stringstream.str(); + + ExecContext const* execContext = ExecContext::CURRENT; + if (execContext == nullptr) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix + << "Permissions are turned off."; + return TRI_ERROR_NO_ERROR; + } + + // collect all used collections in one container + std::set graphCollections; + setUnion(graphCollections, edgeDefinition.getFrom()); + setUnion(graphCollections, edgeDefinition.getTo()); + graphCollections.emplace(edgeDefinition.getName()); + + bool canUseDatabaseRW = execContext->canUseDatabase(auth::Level::RW); + for (auto const& col : graphCollections) { + // We need RO on all collections. And, in case any collection does not + // exist, we need RW on the database. + if (!execContext->canUseCollection(col, auth::Level::RO)) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "No read access to " + << databaseName << "." << col; + return TRI_ERROR_FORBIDDEN; + } + if (!collectionExists(col) && !canUseDatabaseRW) { + LOG_TOPIC(DEBUG, Logger::GRAPHS) << logprefix << "Creation of " + << databaseName << "." << col + << " is not allowed."; + return TRI_ERROR_FORBIDDEN; + } + } + + return TRI_ERROR_NO_ERROR; +} diff --git a/arangod/Graph/GraphOperations.h b/arangod/Graph/GraphOperations.h new file mode 100644 index 0000000000..4ddcff262f --- /dev/null +++ b/arangod/Graph/GraphOperations.h @@ -0,0 +1,211 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Tobias Gödderz & Heiko Kernbach +//////////////////////////////////////////////////////////////////////////////// + +#ifndef ARANGOD_GRAPH_GRAPHOPERATIONS_H +#define ARANGOD_GRAPH_GRAPHOPERATIONS_H + +#include +#include +#include +#include + +#include "Aql/Query.h" +#include "Aql/VariableGenerator.h" +#include "Auth/Common.h" +#include "Basics/ReadWriteLock.h" +#include "Cluster/ClusterInfo.h" +#include "Cluster/ResultT.h" +#include "Graph/Graph.h" +#include "Transaction/Methods.h" +#include "Transaction/StandaloneContext.h" +#include "Utils/OperationResult.h" + +namespace arangodb { +namespace graph { + +// TODO rename to GraphMethods + +class GraphOperations { + private: + Graph& _graph; + TRI_vocbase_t& _vocbase; + + Graph const& graph() const { return _graph; }; + std::shared_ptr ctx() const; + + public: + GraphOperations() = delete; + GraphOperations(Graph& graph_, + TRI_vocbase_t& vocbase) + : _graph(graph_), _vocbase(vocbase) {} + + // TODO I added the complex result type for the get* methods to exactly + // reproduce (in the RestGraphHandler) the behaviour of the similar methods + // in the RestDocumentHandler. A simpler type, e.g. ResultT, + // would be preferable. + + /// @brief Get a single vertex document from collection, optionally check rev + /// The return value is as follows: + /// If trx.begin fails, the outer ResultT will contain this error Result. + /// Otherwise, the results of both (trx.document(), trx.finish()) are + /// returned as a pair. + /// This is because in case of a precondition error during trx.document(), + /// the OperationResult may still be needed. + OperationResult getVertex(std::string const& collectionName, + std::string const& key, + boost::optional rev); + + /// @brief Get a single edge document from definitionName. + /// Similar to getVertex(). + OperationResult getEdge(const std::string& definitionName, + const std::string& key, + boost::optional rev); + + /// @brief Remove a single edge document from definitionName. + OperationResult removeEdge(const std::string& definitionName, + const std::string& key, + boost::optional rev, + bool waitForSync, bool returnOld); + + /// @brief Remove a vertex and all incident edges in the graph + OperationResult removeVertex(const std::string& collectionName, + const std::string& key, + boost::optional rev, + bool waitForSync, bool returnOld); + + OperationResult updateEdge(const std::string& definitionName, + const std::string& key, VPackSlice document, + boost::optional rev, + bool waitForSync, bool returnOld, bool returnNew, + bool keepNull); + + OperationResult replaceEdge(const std::string& definitionName, + const std::string& key, VPackSlice document, + boost::optional rev, + bool waitForSync, bool returnOld, bool returnNew, + bool keepNull); + + OperationResult createEdge(const std::string& definitionName, + VPackSlice document, bool waitForSync, + bool returnNew); + + OperationResult updateVertex(const std::string& collectionName, + const std::string& key, VPackSlice document, + boost::optional rev, + bool waitForSync, bool returnOld, bool returnNew, + bool keepNull); + + OperationResult replaceVertex(const std::string& collectionName, + const std::string& key, VPackSlice document, + boost::optional rev, + bool waitForSync, bool returnOld, + bool returnNew, bool keepNull); + + OperationResult createVertex(const std::string& collectionName, + VPackSlice document, bool waitForSync, + bool returnNew); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief add an orphan to collection to an existing graph + //////////////////////////////////////////////////////////////////////////////// + OperationResult addOrphanCollection(VPackSlice document, bool waitForSync, + bool createCollection); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief remove an orphan collection from an existing graph + //////////////////////////////////////////////////////////////////////////////// + OperationResult eraseOrphanCollection(bool waitForSync, + std::string collectionName, + bool dropCollection); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief create a new edge definition in an existing graph + //////////////////////////////////////////////////////////////////////////////// + OperationResult addEdgeDefinition(VPackSlice edgeDefinition, + bool waitForSync); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief remove an edge definition from an existing graph + //////////////////////////////////////////////////////////////////////////////// + OperationResult eraseEdgeDefinition(bool waitForSync, + std::string edgeDefinitionName, + bool dropCollection); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief create edge definition in an existing graph + //////////////////////////////////////////////////////////////////////////////// + OperationResult editEdgeDefinition(VPackSlice edgeDefinitionSlice, + bool waitForSync, + const std::string& edgeDefinitionName); + + //////////////////////////////////////////////////////////////////////////////// + /// @brief change the edge definition for a specified graph + /// if the graph doesn't already contain a definition for the same edge + /// collection, this does nothing and returns success. + //////////////////////////////////////////////////////////////////////////////// + OperationResult changeEdgeDefinitionForGraph( + const Graph& graph, const EdgeDefinition& edgeDefinition, + bool waitForSync, transaction::Methods& trx); + + private: + using VPackBufferPtr = std::shared_ptr>; + + OperationResult getDocument(std::string const& collectionName, + const std::string& key, + boost::optional rev); + + /// @brief creates a vpack { _key: key } or { _key: key, _rev: rev } + /// (depending on whether rev is set) + VPackBufferPtr _getSearchSlice(const std::string& key, + boost::optional& rev) const; + + OperationResult modifyDocument(const std::string& collectionName, + const std::string& key, VPackSlice document, + bool isPatch, + boost::optional rev, + bool waitForSync, bool returnOld, + bool returnNew, bool keepNull); + + OperationResult createDocument(transaction::Methods* trx, + const std::string& collectionName, + VPackSlice document, bool waitForSync, + bool returnNew); + + OperationResult checkEdgeCollectionAvailability( + std::string edgeCollectionName); + OperationResult checkVertexCollectionAvailability( + std::string vertexCollectionName); + + bool hasROPermissionsFor(std::string const& collection) const; + bool hasRWPermissionsFor(std::string const& collection) const; + bool hasPermissionsFor(std::string const& collection, + auth::Level level) const; + + Result checkEdgeDefinitionPermissions( + EdgeDefinition const& edgeDefinition) const; + + bool collectionExists(std::string const& collection) const; +}; +} // namespace graph +} // namespace arangodb + +#endif // ARANGOD_GRAPH_GRAPHOPERATIONS_H diff --git a/arangod/RestHandler/RestControlPregelHandler.cpp b/arangod/RestHandler/RestControlPregelHandler.cpp index 8b6139d833..f14571509a 100644 --- a/arangod/RestHandler/RestControlPregelHandler.cpp +++ b/arangod/RestHandler/RestControlPregelHandler.cpp @@ -24,7 +24,6 @@ #include "RestControlPregelHandler.h" #include "ApplicationFeatures/ApplicationServer.h" -#include "Aql/Graphs.h" #include "Basics/VelocyPackHelper.h" #include "Cluster/ServerState.h" #include "Pregel/Conductor.h" @@ -33,7 +32,8 @@ #include "Transaction/StandaloneContext.h" #include "V8/v8-vpack.h" #include "V8Server/V8DealerFeature.h" -#include "VocBase/Graphs.h" +#include "Graph/Graph.h" +#include "Graph/GraphManager.h" #include "VocBase/Methods/Tasks.h" #include @@ -133,12 +133,13 @@ void RestControlPregelHandler::startExecution() { return; } - auto ctx = transaction::StandaloneContext::Create(_vocbase); - auto graph = lookupGraphByName(ctx, gs); - if (nullptr == graph) { - generateError(TRI_ERROR_GRAPH_NOT_FOUND); + graph::GraphManager gmngr{_vocbase}; + auto graphRes = gmngr.lookupGraphByName(gs); + if (graphRes.fail()) { + generateError(graphRes.copy_result()); return; } + std::unique_ptr graph = std::move(graphRes.get()); auto gv = graph->vertexCollections(); for (auto& v : gv) { diff --git a/arangod/RestHandler/RestGraphHandler.cpp b/arangod/RestHandler/RestGraphHandler.cpp new file mode 100644 index 0000000000..5fcb115a3d --- /dev/null +++ b/arangod/RestHandler/RestGraphHandler.cpp @@ -0,0 +1,1066 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Tobias Gödderz +//////////////////////////////////////////////////////////////////////////////// + +#include "RestGraphHandler.h" + +#include +#include +#include + +#include "Aql/Query.h" +#include "Basics/VelocyPackHelper.h" +#include "Graph/Graph.h" +#include "Graph/GraphManager.h" +#include "Graph/GraphOperations.h" +#include "RestServer/QueryRegistryFeature.h" +#include "Transaction/StandaloneContext.h" +#include "Utils/OperationOptions.h" +#include "Utils/SingleCollectionTransaction.h" + +using namespace arangodb; +using namespace arangodb::graph; +using VelocyPackHelper = arangodb::basics::VelocyPackHelper; + +RestGraphHandler::RestGraphHandler(GeneralRequest* request, + GeneralResponse* response) + : RestVocbaseBaseHandler(request, response), _gmngr(_vocbase) {} + +RestStatus RestGraphHandler::execute() { + Result res = executeGharial(); + if (res.fail()) { + generateError(res); + return RestStatus::FAIL; + } + // The url is required to properly generate the result! + return RestStatus::DONE; +} + +Result RestGraphHandler::executeGharial() { + auto suffix = request()->suffixes().begin(); + auto end = request()->suffixes().end(); + + auto getNextSuffix = [&suffix]() { return *suffix++; }; + + auto noMoreSuffixes = [&suffix, &end]() { return suffix == end; }; + + if (noMoreSuffixes()) { + // /_api/gharial + return graphsAction(); + } + + std::string const& graphName = getNextSuffix(); + + std::unique_ptr graph = getGraph(graphName); + + // Guaranteed + TRI_ASSERT(graph != nullptr); + + if (noMoreSuffixes()) { + // /_api/gharial/{graph-name} + return graphAction(*(graph.get())); + } + + std::string const& collType = getNextSuffix(); + + const char* vertex = "vertex"; + const char* edge = "edge"; + if (collType != vertex && collType != edge) { + return {TRI_ERROR_HTTP_NOT_FOUND}; + } + + if (noMoreSuffixes()) { + if (collType == vertex) { + // /_api/gharial/{graph-name}/vertex + return vertexSetsAction(*(graph.get())); + } else if (collType == edge) { + // /_api/gharial/{graph-name}/edge + return edgeSetsAction(*(graph.get())); + } + } + + std::string const& setName = getNextSuffix(); + + // TODO Add tests for this, especially with existing collections & vertices + // where the collection is only missing in the graph. + // TODO The existing tests seem to be inconsistent about this: + // e.g., deleting a non-existent vertex collection is expected to throw + // TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST but reading a vertex of a + // non-existent collection is expected to throw + // ERROR_ARANGO_DATA_SOURCE_NOT_FOUND. + // This is commented out until the tests are changed. + // TODO The existing API seems to ignore the type of the collection for + // most operations. So fetching an edge via + // /_api/gharial/{graph}/vertex/{coll}/{key} works just fine. Should this be + // changed? One way or the other, make sure there are tests for the desired + // behaviour! + /* + if (collType == vertex) { + if (graph->vertexCollections().find(setName) == + graph->vertexCollections().end()) { + generateError(TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST); + return RestStatus::DONE; + } + } else if (collType == edge) { + if (graph->edgeCollections().find(setName) == + graph->edgeCollections().end()) { + generateError(TRI_ERROR_GRAPH_EDGE_COL_DOES_NOT_EXIST); + return RestStatus::DONE; + } + } + */ + + if (noMoreSuffixes()) { + if (collType == vertex) { + // /_api/gharial/{graph-name}/vertex/{collection-name} + return vertexSetAction(*(graph.get()), setName); + } else if (collType == edge) { + // /_api/gharial/{graph-name}/edge/{definition-name} + return edgeSetAction(*(graph.get()), setName); + } + } + + std::string const& elementKey = getNextSuffix(); + + if (noMoreSuffixes()) { + if (collType == vertex) { + // /_api/gharial/{graph-name}/vertex/{collection-name}/{vertex-key} + return vertexAction(*(graph.get()), setName, elementKey); + } else if (collType == edge) { + // /_api/gharial/{graph-name}/edge/{definition-name}/{edge-key} + return edgeAction(*(graph.get()), setName, elementKey); + } + } + + return {TRI_ERROR_HTTP_NOT_FOUND}; +} + +Result RestGraphHandler::graphAction( + Graph& graph) { + switch (request()->requestType()) { + case RequestType::GET: + return graphActionReadGraphConfig(graph); + case RequestType::DELETE_REQ: + return graphActionRemoveGraph(graph); + default:; + } + return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED}; +} + +Result RestGraphHandler::graphsAction() { + switch (request()->requestType()) { + case RequestType::GET: + return graphActionReadGraphs(); + case RequestType::POST: + return graphActionCreateGraph(); + default:; + } + return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED}; +} + +Result RestGraphHandler::vertexSetsAction( + Graph& graph) { + + switch (request()->requestType()) { + case RequestType::GET: + return graphActionReadConfig(graph, TRI_COL_TYPE_DOCUMENT, GraphProperty::VERTICES); + case RequestType::POST: + return modifyVertexDefinition(graph, VertexDefinitionAction::CREATE, ""); + default:; + } + return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED}; +} + +Result RestGraphHandler::edgeSetsAction( + Graph& graph) { + + switch (request()->requestType()) { + case RequestType::GET: + return graphActionReadConfig(graph, TRI_COL_TYPE_EDGE, GraphProperty::EDGES); + case RequestType::POST: + return createEdgeDefinition(graph); + default:; + } + return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED}; +} + +Result RestGraphHandler::edgeSetAction( + Graph& graph, + const std::string& edgeDefinitionName) { + + switch (request()->requestType()) { + case RequestType::POST: + return edgeActionCreate(graph, edgeDefinitionName); + case RequestType::PUT: + return editEdgeDefinition(graph, edgeDefinitionName); + case RequestType::DELETE_REQ: + return removeEdgeDefinition(graph, edgeDefinitionName); + default:; + } + return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED}; +} + +Result RestGraphHandler::vertexSetAction( + Graph& graph, + const std::string& vertexCollectionName) { + + switch (request()->requestType()) { + case RequestType::POST: + return vertexActionCreate(graph, vertexCollectionName); + case RequestType::DELETE_REQ: + return modifyVertexDefinition(graph, VertexDefinitionAction::REMOVE, vertexCollectionName); + default:; + } + return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED}; +} + +Result RestGraphHandler::vertexAction( + Graph& graph, + const std::string& vertexCollectionName, const std::string& vertexKey) { + + switch (request()->requestType()) { + case RequestType::GET: { + vertexActionRead(graph, vertexCollectionName, vertexKey); + return {TRI_ERROR_NO_ERROR}; + } + case RequestType::PATCH: + return vertexActionUpdate(graph, vertexCollectionName, vertexKey); + case RequestType::PUT: + return vertexActionReplace(graph, vertexCollectionName, vertexKey); + case RequestType::DELETE_REQ: + return vertexActionRemove(graph, vertexCollectionName, vertexKey); + default:; + } + return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED}; +} + +Result RestGraphHandler::edgeAction( + Graph& graph, + const std::string& edgeDefinitionName, const std::string& edgeKey) { + + switch (request()->requestType()) { + case RequestType::GET: + edgeActionRead(graph, edgeDefinitionName, edgeKey); + return {TRI_ERROR_NO_ERROR}; + case RequestType::DELETE_REQ: + return edgeActionRemove(graph, edgeDefinitionName, edgeKey); + case RequestType::PATCH: + return edgeActionUpdate(graph, edgeDefinitionName, edgeKey); + break; + case RequestType::PUT: + return edgeActionReplace(graph, edgeDefinitionName, edgeKey); + default:; + } + return {TRI_ERROR_HTTP_METHOD_NOT_ALLOWED}; +} + +void RestGraphHandler::vertexActionRead( + Graph& graph, std::string const& collectionName, + std::string const& key) { + + bool isValidRevision; + TRI_voc_rid_t revision = extractRevision("if-match", isValidRevision); + if (!isValidRevision) { + revision = + UINT64_MAX; // an impossible rev, so precondition failed will happen + } + auto maybeRev = boost::make_optional(revision != 0, revision); + + GraphOperations gops{graph, _vocbase}; + OperationResult result = gops.getVertex(collectionName, key, maybeRev); + + if (!result.ok()) { + if (result.is(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND)) { + generateDocumentNotFound(collectionName, key); + } else if (maybeRev && result.is(TRI_ERROR_ARANGO_CONFLICT)) { + generatePreconditionFailed(result.slice()); + } else { + generateTransactionError(collectionName, result.result, key); + } + return; + } + + auto ctx = std::make_shared(_vocbase); + // use default options + generateVertexRead(result.slice(), *ctx->getVPackOptionsForDump()); +} + +/// @brief generate response object: { error, code, vertex } +void RestGraphHandler::generateVertexRead(VPackSlice vertex, + VPackOptions const& options) { + vertex = vertex.resolveExternal(); + resetResponse(rest::ResponseCode::OK); + addEtagHeader(vertex.get(StaticStrings::RevString)); + generateResultWithField("vertex", vertex, options); +} + +/// @brief generate response object: { error, code, edge } +void RestGraphHandler::generateEdgeRead(VPackSlice edge, + VPackOptions const& options) { + edge = edge.resolveExternal(); + resetResponse(rest::ResponseCode::OK); + addEtagHeader(edge.get(StaticStrings::RevString)); + generateResultWithField("edge", edge, options); +} + +/// @brief generate response object: { error, code, removed, old? } +/// "old" is omitted if old is a NoneSlice. +void RestGraphHandler::generateRemoved(bool removed, bool wasSynchronous, + VPackSlice old, + VPackOptions const& options) { + ResponseCode code; + if (wasSynchronous) { + code = rest::ResponseCode::OK; + } else { + code = rest::ResponseCode::ACCEPTED; + } + resetResponse(code); + VPackBuilder obj; + obj.add(VPackValue(VPackValueType::Object, true)); + obj.add("removed", VPackValue(removed)); + if (!old.isNone()) { + obj.add("old", old); + } + obj.close(); + generateResultMergedWithObject(obj.slice(), options); +} + +/// @brief generate response object: { error, code, removed, old? } +/// "old" is omitted if old is a NoneSlice. +void RestGraphHandler::generateGraphRemoved(bool removed, bool wasSynchronous, + VPackOptions const& options) { + ResponseCode code; + if (wasSynchronous) { + code = rest::ResponseCode::ACCEPTED; + } else { + code = rest::ResponseCode::ACCEPTED; + // code = rest::ResponseCode::CREATED; // TODO fix this in a major release upgrade + } + resetResponse(code); + VPackBuilder obj; + obj.add(VPackValue(VPackValueType::Object, true)); + obj.add("removed", VPackValue(removed)); + obj.close(); + generateResultMergedWithObject(obj.slice(), options); +} + +void RestGraphHandler::generateGraphConfig(VPackSlice slice, + VPackOptions const& options) { + resetResponse(rest::ResponseCode::OK); + generateResultMergedWithObject(slice, options); +} + +void RestGraphHandler::generateCreatedGraphConfig(bool wasSynchronous, VPackSlice slice, + VPackOptions const& options) { + ResponseCode code; + if (wasSynchronous) { + code = rest::ResponseCode::CREATED; + } else { + code = rest::ResponseCode::ACCEPTED; + } + resetResponse(code); + addEtagHeader(slice.get("graph").get(StaticStrings::RevString)); + generateResultMergedWithObject(slice, options); +} + +void RestGraphHandler::generateCreatedEdgeDefinition(bool wasSynchronous, VPackSlice slice, + VPackOptions const& options) { + ResponseCode code; + if (wasSynchronous) { + code = rest::ResponseCode::ACCEPTED; // TODO: fix this in a major upgrade release + } else { + code = rest::ResponseCode::ACCEPTED; + } + resetResponse(code); + addEtagHeader(slice.get("graph").get(StaticStrings::RevString)); + generateResultMergedWithObject(slice, options); +} + +/// @brief generate response object: { error, code, vertex, old?, new? } +void RestGraphHandler::generateVertexModified( + bool wasSynchronous, VPackSlice resultSlice, + const velocypack::Options& options) { + generateModified(TRI_COL_TYPE_DOCUMENT, wasSynchronous, resultSlice, options); +} + +/// @brief generate response object: { error, code, vertex } +void RestGraphHandler::generateVertexCreated( + bool wasSynchronous, VPackSlice resultSlice, + const velocypack::Options& options) { + generateCreated(TRI_COL_TYPE_DOCUMENT, wasSynchronous, resultSlice, options); +} + +/// @brief generate response object: { error, code, edge, old?, new? } +void RestGraphHandler::generateEdgeModified( + bool wasSynchronous, VPackSlice resultSlice, + const velocypack::Options& options) { + generateModified(TRI_COL_TYPE_EDGE, wasSynchronous, resultSlice, options); +} + +/// @brief generate response object: { error, code, edge } +void RestGraphHandler::generateEdgeCreated( + bool wasSynchronous, VPackSlice resultSlice, + const velocypack::Options& options) { + generateCreated(TRI_COL_TYPE_EDGE, wasSynchronous, resultSlice, options); +} + +/// @brief generate response object: { error, code, vertex/edge, old?, new? } +// TODO Maybe a class enum in Graph.h to discern Vertex/Edge is better than +// abusing document/edge collection types? +void RestGraphHandler::generateModified(TRI_col_type_e colType, + bool wasSynchronous, + VPackSlice resultSlice, + const velocypack::Options& options) { + TRI_ASSERT(colType == TRI_COL_TYPE_DOCUMENT || colType == TRI_COL_TYPE_EDGE); + if (wasSynchronous) { + resetResponse(rest::ResponseCode::OK); + } else { + resetResponse(rest::ResponseCode::ACCEPTED); + } + addEtagHeader(resultSlice.get(StaticStrings::RevString)); + + const char* objectTypeName = "_"; + if (colType == TRI_COL_TYPE_DOCUMENT) { + objectTypeName = "vertex"; + } else if (colType == TRI_COL_TYPE_EDGE) { + objectTypeName = "edge"; + } + + VPackBuilder objectBuilder = VPackCollection::remove( + resultSlice, std::unordered_set{"old", "new"}); + // Note: This doesn't really contain the object, only _id, _key, _rev, _oldRev + VPackSlice objectSlice = objectBuilder.slice(); + VPackSlice oldSlice = resultSlice.get("old"); + VPackSlice newSlice = resultSlice.get("new"); + + VPackBuilder obj; + obj.add(VPackValue(VPackValueType::Object, true)); + obj.add(objectTypeName, objectSlice); + if (!oldSlice.isNone()) { + obj.add("old", oldSlice); + } + if (!newSlice.isNone()) { + obj.add("new", newSlice); + } + obj.close(); + generateResultMergedWithObject(obj.slice(), options); +} + +/// @brief generate response object: { error, code, vertex/edge } +// TODO Maybe a class enum in Graph.h to discern Vertex/Edge is better than +// abusing document/edge collection types? +void RestGraphHandler::generateCreated(TRI_col_type_e colType, + bool wasSynchronous, + VPackSlice resultSlice, + const velocypack::Options& options) { + TRI_ASSERT(colType == TRI_COL_TYPE_DOCUMENT || colType == TRI_COL_TYPE_EDGE); + if (wasSynchronous) { + resetResponse(rest::ResponseCode::CREATED); + } else { + resetResponse(rest::ResponseCode::ACCEPTED); + } + addEtagHeader(resultSlice.get(StaticStrings::RevString)); + + const char* objectTypeName = "_"; + if (colType == TRI_COL_TYPE_DOCUMENT) { + objectTypeName = "vertex"; + } else if (colType == TRI_COL_TYPE_EDGE) { + objectTypeName = "edge"; + } + + VPackBuilder objectBuilder = VPackCollection::remove( + resultSlice, std::unordered_set{"old", "new"}); + // Note: This doesn't really contain the object, only _id, _key, _rev, _oldRev + VPackSlice objectSlice = objectBuilder.slice(); + VPackSlice newSlice = resultSlice.get("new"); + + VPackBuilder obj; + obj.add(VPackValue(VPackValueType::Object, true)); + obj.add(objectTypeName, objectSlice); + if (!newSlice.isNone()) { + obj.add("new", newSlice); + } + obj.close(); + generateResultMergedWithObject(obj.slice(), options); +} + +/// @brief generate response object: { error, code, key: value } +void RestGraphHandler::generateResultWithField(std::string const& key, + VPackSlice value, + VPackOptions const& options) { + VPackBuilder obj; + obj.add(VPackValue(VPackValueType::Object, true)); + obj.add(key, value); + obj.close(); + generateResultMergedWithObject(obj.slice(), options); +} + +/// @brief generate response object: MERGE({ error, code }, obj) +void RestGraphHandler::generateResultMergedWithObject( + VPackSlice obj, VPackOptions const& options) { + _response->setContentType(_request->contentTypeResponse()); + + try { + VPackBuilder result; + result.add(VPackValue(VPackValueType::Object, true)); + result.add(StaticStrings::Error, VPackValue(false)); + result.add(StaticStrings::Code, + VPackValue(static_cast(_response->responseCode()))); + result.close(); + VPackBuilder merged = + VelocyPackHelper::merge(result.slice(), obj, false, false); + + writeResult(std::move(*merged.buffer().get()), options); + } catch (...) { + // Building the error response failed + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_INTERNAL, + "cannot generate output"); + } +} + +// TODO this is nearly exactly the same as vertexActionRead. reuse somehow? +void RestGraphHandler::edgeActionRead( + Graph& graph, + const std::string &definitionName, const std::string &key) { + + bool isValidRevision; + TRI_voc_rid_t revision = extractRevision("if-match", isValidRevision); + if (!isValidRevision) { + revision = + UINT64_MAX; // an impossible rev, so precondition failed will happen + } + auto maybeRev = boost::make_optional(revision != 0, revision); + + GraphOperations gops{graph, _vocbase}; + OperationResult result = gops.getEdge(definitionName, key, maybeRev); + + if (result.fail()) { + generateTransactionError(result); + return; + } + + auto ctx = std::make_shared(_vocbase); + generateEdgeRead(result.slice(), *ctx->getVPackOptionsForDump()); +} + +std::unique_ptr RestGraphHandler::getGraph(const std::string& graphName) { + auto graphResult = _gmngr.lookupGraphByName(graphName); + if (graphResult.fail()) { + THROW_ARANGO_EXCEPTION(graphResult); + } + TRI_ASSERT(graphResult.get() != nullptr); + return std::move(graphResult.get()); +} + +// TODO this is very similar to (edge|vertex)ActionRead. find a way to reduce +// the duplicate code. +// TODO The tests check that, if "returnOld: true" is passed, the result +// contains the old value in the field "old". This is not documented in +// HTTP/Gharial! +Result RestGraphHandler::edgeActionRemove( + Graph& graph, const std::string& definitionName, + const std::string& key) { + + bool waitForSync = + _request->parsedValue(StaticStrings::WaitForSyncString, false); + + bool returnOld = _request->parsedValue(StaticStrings::ReturnOldString, false); + + bool isValidRevision; + TRI_voc_rid_t revision = extractRevision("if-match", isValidRevision); + if (!isValidRevision) { + revision = + UINT64_MAX; // an impossible rev, so precondition failed will happen + } + auto maybeRev = boost::make_optional(revision != 0, revision); + + GraphOperations gops{graph, _vocbase}; + + OperationResult result = + gops.removeEdge(definitionName, key, maybeRev, waitForSync, returnOld); + + if (result.fail()) { + generateTransactionError(result); + return result.result; + } + + auto ctx = std::make_shared(_vocbase); + generateRemoved(true, result._options.waitForSync, result.slice().get("old"), + *ctx->getVPackOptionsForDump()); + + return Result(); +} + +/// @brief If rev is a string, set the Etag header to its value. +/// rev is expected to be either None or a string. +void RestGraphHandler::addEtagHeader(velocypack::Slice rev) { + TRI_ASSERT(rev.isString() || rev.isNone()); + if (rev.isString()) { + _response->setHeaderNC(StaticStrings::Etag, rev.copyString()); + } +} + +Result RestGraphHandler::vertexActionUpdate( + graph::Graph& graph, + const std::string& collectionName, const std::string& key) { + return vertexModify(graph, collectionName, key, true); +} + +Result RestGraphHandler::vertexActionReplace( + graph::Graph& graph, + const std::string& collectionName, const std::string& key) { + return vertexModify(graph, collectionName, key, false); +} + +Result RestGraphHandler::vertexActionCreate( + graph::Graph& graph, + const std::string& collectionName) { + return vertexCreate(graph, collectionName); +} + +Result RestGraphHandler::edgeActionUpdate( + graph::Graph& graph, + const std::string& collectionName, const std::string& key) { + return edgeModify(graph, collectionName, key, true); +} + +Result RestGraphHandler::edgeActionReplace( + graph::Graph& graph, + const std::string& collectionName, const std::string& key) { + return edgeModify(graph, collectionName, key, false); +} + +Result RestGraphHandler::edgeModify(graph::Graph& graph, + const std::string& collectionName, + const std::string& key, bool isPatch) { + return documentModify(graph, collectionName, key, isPatch, + TRI_COL_TYPE_EDGE); +} + +Result RestGraphHandler::edgeCreate(graph::Graph& graph, + const std::string& collectionName) { + return documentCreate(graph, collectionName, + TRI_COL_TYPE_EDGE); +} + +Result RestGraphHandler::edgeActionCreate( + graph::Graph& graph, + const std::string& collectionName) { + return edgeCreate(graph, collectionName); +} + +Result RestGraphHandler::vertexModify(graph::Graph& graph, + const std::string& collectionName, + const std::string& key, bool isPatch) { + return documentModify(graph, collectionName, key, isPatch, + TRI_COL_TYPE_DOCUMENT); +} + +Result RestGraphHandler::vertexCreate(graph::Graph& graph, + const std::string& collectionName) { + return documentCreate(graph, collectionName, + TRI_COL_TYPE_DOCUMENT); +} + +// /_api/gharial/{graph-name}/edge/{definition-name} +Result RestGraphHandler::editEdgeDefinition( + graph::Graph& graph, + const std::string& edgeDefinitionName) { + return modifyEdgeDefinition(graph, EdgeDefinitionAction::EDIT, + edgeDefinitionName); +} + +Result RestGraphHandler::createEdgeDefinition( + graph::Graph& graph) { + return modifyEdgeDefinition(graph, EdgeDefinitionAction::CREATE); +} + +// /_api/gharial/{graph-name}/edge +Result RestGraphHandler::modifyEdgeDefinition(graph::Graph& graph, + EdgeDefinitionAction action, std::string edgeDefinitionName) { + + // edgeDefinitionName == "" <=> action == CREATE + TRI_ASSERT((action == EdgeDefinitionAction::CREATE) + == edgeDefinitionName.empty()); + bool parseSuccess = false; + VPackSlice body = this->parseVPackBody(parseSuccess); + if (!parseSuccess) { + return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"}; + } + bool waitForSync = + _request->parsedValue(StaticStrings::WaitForSyncString, false); + bool dropCollections = + _request->parsedValue(StaticStrings::GraphDropCollections, false); + + GraphOperations gops{graph, _vocbase}; + OperationResult result; + + if (action == EdgeDefinitionAction::CREATE) { + result = gops.addEdgeDefinition(body, waitForSync); + } else if (action == EdgeDefinitionAction::EDIT) { + result = gops.editEdgeDefinition(body, waitForSync, edgeDefinitionName); + } else if (action == EdgeDefinitionAction::REMOVE) { + // TODO Does this get waitForSync? Not according to the documentation. + // if not, remove the parameter from eraseEdgeDefinition. What about add/edit? + result = gops.eraseEdgeDefinition( + waitForSync, edgeDefinitionName, dropCollections + ); + } else { + TRI_ASSERT(false); + } + + if (result.fail()) { + generateTransactionError(result); + return result.result; + } + + auto ctx = std::make_shared(_vocbase); + + auto newGraph = getGraph(graph.name()); + TRI_ASSERT(newGraph != nullptr); + VPackBuilder builder; + builder.openObject(); + newGraph->graphForClient(builder); + builder.close(); + + generateCreatedEdgeDefinition(waitForSync, builder.slice(), *ctx->getVPackOptionsForDump()); + + return Result(); +} + +Result RestGraphHandler::modifyVertexDefinition(graph::Graph& graph, + VertexDefinitionAction action, std::string vertexDefinitionName) { + bool parseSuccess = false; + VPackSlice body = this->parseVPackBody(parseSuccess); + if (!parseSuccess) { + return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"}; + } + + // TODO maybe merge this function with modifyEdgeDefinition? + bool waitForSync = + _request->parsedValue(StaticStrings::WaitForSyncString, false); + bool dropCollection = + _request->parsedValue(StaticStrings::GraphDropCollection, false); + bool createCollection = + _request->parsedValue(StaticStrings::GraphCreateCollection, true); + + GraphOperations gops{graph, _vocbase}; + OperationResult result; + + if (action == VertexDefinitionAction::CREATE) { + result = gops.addOrphanCollection(body, waitForSync, createCollection); + } else if (action == VertexDefinitionAction::REMOVE) { + result = gops.eraseOrphanCollection( + waitForSync, vertexDefinitionName, dropCollection + ); + } else { + TRI_ASSERT(false); + } + + if (result.fail()) { + generateTransactionError(result); + return result.result; + } + + auto ctx = std::make_shared(_vocbase); + + auto newGraph = getGraph(graph.name()); + TRI_ASSERT(newGraph != nullptr); + VPackBuilder builder; + builder.openObject(); + newGraph->graphForClient(builder); + builder.close(); + + generateCreatedEdgeDefinition(waitForSync, builder.slice(), *ctx->getVPackOptionsForDump()); + + return Result(); +} + +Result RestGraphHandler::removeEdgeDefinition(graph::Graph& graph, + const std::string& edgeDefinitionName) { + return modifyEdgeDefinition(graph, EdgeDefinitionAction::REMOVE, edgeDefinitionName); +} + + +// TODO The tests check that, if "returnOld: true" is passed, the result +// contains the old value in the field "old"; and if "returnNew: true" is +// passed, the field "new" contains the new value (along with "vertex"!). +// This is not documented in HTTP/Gharial! +// TODO the document API also supports mergeObjects, silent and ignoreRevs; +// should gharial, too? +Result RestGraphHandler::documentModify( + graph::Graph& graph, + const std::string& collectionName, const std::string& key, bool isPatch, + TRI_col_type_e colType) { + + bool parseSuccess = false; + VPackSlice body = this->parseVPackBody(parseSuccess); + if (!parseSuccess) { + return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"}; + } + + bool waitForSync = + _request->parsedValue(StaticStrings::WaitForSyncString, false); + bool returnNew = _request->parsedValue(StaticStrings::ReturnNewString, false); + bool returnOld = _request->parsedValue(StaticStrings::ReturnOldString, false); + // Note: the default here differs from the one in the RestDoumentHandler + bool keepNull = _request->parsedValue(StaticStrings::KeepNullString, true); + + // extract the revision, if single document variant and header given: + std::unique_ptr builder; + TRI_voc_rid_t revision = 0; + bool isValidRevision; + revision = extractRevision("if-match", isValidRevision); + if (!isValidRevision) { + revision = UINT64_MAX; // an impossible revision, so precondition failed + } + auto maybeRev = boost::make_optional(revision != 0, revision); + + GraphOperations gops{graph, _vocbase}; + + OperationResult result; + // TODO get rid of this branching, rather use several functions and reuse the + // common code another way. + if (isPatch && colType == TRI_COL_TYPE_DOCUMENT) { + result = gops.updateVertex(collectionName, key, body, maybeRev, + waitForSync, returnOld, returnNew, keepNull); + } else if (!isPatch && colType == TRI_COL_TYPE_DOCUMENT) { + result = gops.replaceVertex(collectionName, key, body, maybeRev, + waitForSync, returnOld, returnNew, keepNull); + } else if (isPatch && colType == TRI_COL_TYPE_EDGE) { + result = gops.updateEdge(collectionName, key, body, maybeRev, waitForSync, + returnOld, returnNew, keepNull); + } else if (!isPatch && colType == TRI_COL_TYPE_EDGE) { + result = gops.replaceEdge(collectionName, key, body, maybeRev, waitForSync, + returnOld, returnNew, keepNull); + } else { + TRI_ASSERT(false); + } + + if (result.fail()) { + generateTransactionError(result); + return result.result; + } + + auto ctx = std::make_shared(_vocbase); + switch (colType) { + case TRI_COL_TYPE_DOCUMENT: + generateVertexModified(result._options.waitForSync, result.slice(), + *ctx->getVPackOptionsForDump()); + break; + case TRI_COL_TYPE_EDGE: + generateEdgeModified(result._options.waitForSync, result.slice(), + *ctx->getVPackOptionsForDump()); + break; + default: + TRI_ASSERT(false); + } + + return TRI_ERROR_NO_ERROR; +} + +Result RestGraphHandler::documentCreate( + graph::Graph& graph, + const std::string& collectionName, + TRI_col_type_e colType) { + + bool parseSuccess = false; + VPackSlice body = this->parseVPackBody(parseSuccess); + if (!parseSuccess) { + return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"}; + } + + bool waitForSync = + _request->parsedValue(StaticStrings::WaitForSyncString, false); + bool returnNew = _request->parsedValue(StaticStrings::ReturnNewString, false); + + GraphOperations gops{graph, _vocbase}; + + OperationResult result; + if (colType == TRI_COL_TYPE_DOCUMENT) { + result = gops.createVertex(collectionName, body, waitForSync, returnNew); + } else if (colType == TRI_COL_TYPE_EDGE) { + result = gops.createEdge(collectionName, body, waitForSync, returnNew); + } else { + TRI_ASSERT(false); + } + + if (result.fail()) { + // need to call more detailed constructor here + generateTransactionError(collectionName, result, "", 0); + return result.result; + } + + auto ctx = std::make_shared(_vocbase); + switch (colType) { + case TRI_COL_TYPE_DOCUMENT: + generateVertexCreated(result._options.waitForSync, result.slice(), + *ctx->getVPackOptionsForDump()); + break; + case TRI_COL_TYPE_EDGE: + generateEdgeCreated(result._options.waitForSync, result.slice(), + *ctx->getVPackOptionsForDump()); + break; + default: + TRI_ASSERT(false); + } + + return TRI_ERROR_NO_ERROR; +} + +Result RestGraphHandler::vertexActionRemove( + graph::Graph& graph, + const std::string& collectionName, const std::string& key) { + + bool waitForSync = + _request->parsedValue(StaticStrings::WaitForSyncString, false); + + bool returnOld = _request->parsedValue(StaticStrings::ReturnOldString, false); + + bool isValidRevision; + TRI_voc_rid_t revision = extractRevision("if-match", isValidRevision); + if (!isValidRevision) { + revision = + UINT64_MAX; // an impossible rev, so precondition failed will happen + } + auto maybeRev = boost::make_optional(revision != 0, revision); + + GraphOperations gops{graph, _vocbase}; + + OperationResult result = + gops.removeVertex(collectionName, key, maybeRev, waitForSync, returnOld); + + if (result.fail()) { + generateTransactionError(result); + return result.result; + } + + auto ctx = std::make_shared(_vocbase); + generateRemoved(true, result._options.waitForSync, result.slice().get("old"), + *ctx->getVPackOptionsForDump()); + + return Result(); +} + +Result RestGraphHandler::graphActionReadGraphConfig( + graph::Graph const& graph) { + + auto ctx = std::make_shared(_vocbase); + VPackBuilder builder; + builder.openObject(); + graph.graphForClient(builder); + builder.close(); + generateGraphConfig(builder.slice(), *ctx->getVPackOptionsForDump()); + + return Result(); +} + +Result RestGraphHandler::graphActionRemoveGraph( + graph::Graph const& graph) { + bool waitForSync = + _request->parsedValue(StaticStrings::WaitForSyncString, false); + bool dropCollections = + _request->parsedValue(StaticStrings::GraphDropCollections, false); + + OperationResult result = + _gmngr.removeGraph(graph, waitForSync, dropCollections); + + if (result.fail()) { + generateTransactionError(result); + return result.result; + } + + auto ctx = std::make_shared(_vocbase); + generateGraphRemoved(true, result._options.waitForSync, + *ctx->getVPackOptionsForDump()); + + return Result(); +} + +Result RestGraphHandler::graphActionCreateGraph() { + + bool parseSuccess = false; + VPackSlice body = this->parseVPackBody(parseSuccess); + if (!parseSuccess) { + return {TRI_ERROR_BAD_PARAMETER, "unable to parse body"}; + } + bool waitForSync = + _request->parsedValue(StaticStrings::WaitForSyncString, false); + + { + OperationResult result = _gmngr.createGraph(body, waitForSync); + + if (result.fail()) { + generateTransactionError(result); + return result.result; + } + } + + std::string graphName = body.get(StaticStrings::DataSourceName).copyString(); + + auto ctx = std::make_shared(_vocbase); + std::unique_ptr graph = getGraph(graphName); + TRI_ASSERT(graph != nullptr); + + VPackBuilder builder; + builder.openObject(); + graph->graphForClient(builder); + builder.close(); + + generateCreatedGraphConfig(waitForSync, builder.slice(), *ctx->getVPackOptionsForDump()); + + return Result(); +} + +Result RestGraphHandler::graphActionReadGraphs() { + auto ctx = std::make_shared(_vocbase); + + VPackBuilder builder; + _gmngr.readGraphs(builder, arangodb::aql::PART_MAIN); + + generateGraphConfig(builder.slice(), *ctx->getVPackOptionsForDump()); + + return Result(); +} + +Result RestGraphHandler::graphActionReadConfig( + graph::Graph const& graph, TRI_col_type_e colType, + GraphProperty property) { + VPackBuilder builder; + + if (colType == TRI_COL_TYPE_DOCUMENT && property == GraphProperty::VERTICES) { + graph.verticesToVpack(builder); + } else if (colType == TRI_COL_TYPE_EDGE && property == GraphProperty::EDGES) { + graph.edgesToVpack(builder); + } else { + TRI_ASSERT(false); + } + + auto ctx = std::make_shared(_vocbase); + + generateGraphConfig(builder.slice(), *ctx->getVPackOptionsForDump()); + + return Result(); +} + +RequestLane RestGraphHandler::lane() const { + return RequestLane::CLIENT_SLOW; +} diff --git a/arangod/RestHandler/RestGraphHandler.h b/arangod/RestHandler/RestGraphHandler.h new file mode 100644 index 0000000000..213d479113 --- /dev/null +++ b/arangod/RestHandler/RestGraphHandler.h @@ -0,0 +1,278 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2018 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Tobias Gödderz +//////////////////////////////////////////////////////////////////////////////// + +#ifndef ARANGOD_REST_HANDLER_REST_GRAPH_HANDLER_H +#define ARANGOD_REST_HANDLER_REST_GRAPH_HANDLER_H + +#include + +#include "Actions/RestActionHandler.h" +#include "Graph/GraphManager.h" +#include "RestHandler/RestBaseHandler.h" + +namespace arangodb { + +namespace graph { +class Graph; +} + +class RestGraphHandler : public arangodb::RestVocbaseBaseHandler { + private: + enum class GraphProperty { + VERTICES, EDGES + }; + + enum class EdgeDefinitionAction { + CREATE, EDIT, REMOVE + }; + + enum class VertexDefinitionAction { + CREATE, REMOVE + }; + + public: + RestGraphHandler(GeneralRequest* request, GeneralResponse* response); + + ~RestGraphHandler() override = default; + + char const* name() const final { return "RestGraphHandler"; } + + RestStatus execute() override; + + RequestLane lane() const override; + + private: + arangodb::Result executeGharial(); + + // /_api/gharial + arangodb::Result graphsAction(); + + // /_api/gharial/{graph-name} + arangodb::Result graphAction( + graph::Graph& graph); + + // /_api/gharial/{graph-name}/vertex + arangodb::Result vertexSetsAction( + graph::Graph& graph); + + // /_api/gharial/{graph-name}/edge + arangodb::Result edgeSetsAction( + graph::Graph& graph); + + // /_api/gharial/{graph-name}/vertex/{collection-name} + arangodb::Result vertexSetAction( + graph::Graph& graph, + const std::string& vertexCollectionName); + + // /_api/gharial/{graph-name}/edge/{definition-name} + arangodb::Result edgeSetAction( + graph::Graph& graph, + const std::string& edgeDefinitionName); + + // /_api/gharial/{graph-name}/vertex/{collection-name}/{vertex-key} + arangodb::Result vertexAction( + graph::Graph& graph, + const std::string& vertexCollectionName, const std::string& vertexKey); + + // /_api/gharial/{graph-name}/edge/{definition-name}/{edge-key} + arangodb::Result edgeAction( + graph::Graph& graph, + const std::string& edgeDefinitionName, const std::string& edgeKey); + + // GET /_api/gharial/{graph-name}/vertex/{collection-name}/{vertex-key} + void vertexActionRead(graph::Graph& graph, + const std::string &collectionName, + const std::string &key); + + // DELETE /_api/gharial/{graph-name}/vertex/{collection-name}/{vertex-key} + arangodb::Result vertexActionRemove(graph::Graph& graph, + const std::string& collectionName, + const std::string& key); + + // PATCH /_api/gharial/{graph-name}/vertex/{collection-name}/{vertex-key} + arangodb::Result vertexActionUpdate(graph::Graph& graph, + const std::string& collectionName, + const std::string& key); + + // PUT /_api/gharial/{graph-name}/vertex/{collection-name}/{vertex-key} + arangodb::Result vertexActionReplace(graph::Graph& graph, + const std::string& collectionName, + const std::string& key); + + // POST /_api/gharial/{graph-name}/vertex/{collection-name}/{vertex-key} + arangodb::Result vertexActionCreate(graph::Graph& graph, + const std::string& collectionName); + + // GET /_api/gharial/{graph-name}/edge/{definition-name}/{edge-key} + void edgeActionRead(graph::Graph& graph, + const std::string &definitionName, + const std::string &key); + + // DELETE /_api/gharial/{graph-name}/edge/{definition-name}/{edge-key} + arangodb::Result edgeActionRemove(graph::Graph& graph, + const std::string& definitionName, + const std::string& key); + + // POST /_api/gharial/{graph-name}/edge/{definition-name}/{edge-key} + arangodb::Result edgeActionCreate(graph::Graph& graph, + const std::string& definitionName); + + // PATCH /_api/gharial/{graph-name}/edge/{definition-name}/{edge-key} + arangodb::Result edgeActionUpdate(graph::Graph& graph, + const std::string& collectionName, + const std::string& key); + + // PUT /_api/gharial/{graph-name}/edge/{definition-name}/{edge-key} + arangodb::Result edgeActionReplace(graph::Graph& graph, + const std::string& collectionName, + const std::string& key); + + std::unique_ptr getGraph(const std::string& graphName); + + void generateVertexRead(VPackSlice vertex, VPackOptions const& options); + + void generateEdgeRead(VPackSlice edge, const VPackOptions& options); + + void generateRemoved(bool removed, bool wasSynchronous, VPackSlice old, + VPackOptions const& options); + + void generateGraphRemoved(bool removed, bool wasSynchronous, + VPackOptions const& options); + + void generateCreatedGraphConfig(bool wasSynchronous, VPackSlice slice, + VPackOptions const& options); + + void generateCreatedEdgeDefinition(bool wasSynchronous, VPackSlice slice, + VPackOptions const& options); + + void generateGraphConfig(VPackSlice slice, VPackOptions const& options); + + // TODO maybe cleanup the generate* zoo a little? + void generateResultWithField(std::string const& key, VPackSlice value, + VPackOptions const& options); + + void generateResultMergedWithObject(VPackSlice obj, + VPackOptions const& options); + + void addEtagHeader(velocypack::Slice slice); + + Result vertexModify(graph::Graph& graph, + const std::string& collectionName, const std::string& key, + bool isPatch); + + Result vertexCreate(graph::Graph& graph, + const std::string& collectionName); + + void generateVertexModified(bool wasSynchronous, VPackSlice resultSlice, + const velocypack::Options& options); + + void generateVertexCreated(bool wasSynchronous, VPackSlice resultSlice, + const velocypack::Options& options); + + void generateModified(TRI_col_type_e colType, bool wasSynchronous, + VPackSlice resultSlice, + const velocypack::Options& options); + + void generateCreated(TRI_col_type_e colType, bool wasSynchronous, + VPackSlice resultSlice, + const velocypack::Options& options); + + Result edgeModify(graph::Graph& graph, + const std::string& collectionName, const std::string& key, + bool isPatch); + + Result edgeCreate(graph::Graph& graph, + const std::string& collectionName); + + Result documentModify(graph::Graph& graph, + const std::string& collectionName, + const std::string& key, bool isPatch, + TRI_col_type_e colType); + + Result documentCreate( + graph::Graph& graph, const std::string &collectionName, + TRI_col_type_e colType + ); + + Result graphActionReadGraphConfig( + graph::Graph const& graph + ); + + Result graphActionRemoveGraph( + graph::Graph const& graph + ); + + Result graphActionCreateGraph(); + Result graphActionReadGraphs(); + + Result graphActionReadConfig( + graph::Graph const& graph, + TRI_col_type_e colType, GraphProperty property + ); + + void generateEdgeModified( + bool wasSynchronous, VPackSlice resultSlice, + velocypack::Options const& options + ); + + void generateEdgeCreated( + bool wasSynchronous, VPackSlice resultSlice, + velocypack::Options const& options + ); + + // edges + // PATCH /_api/gharial/{graph-name}/edge/{definition-name} + Result editEdgeDefinition( + graph::Graph& graph, + const std::string& edgeDefinitionName + ); + + // DELETE /_api/gharial/{graph-name}/edge/{definition-name} + Result removeEdgeDefinition( + graph::Graph& graph, + const std::string& edgeDefinitionName + ); + + // POST /_api/gharial/{graph-name}/edge/ + Result createEdgeDefinition( + graph::Graph& graph + ); + + // edgeDefinitionName may be omitted when action == CREATE + Result modifyEdgeDefinition( + graph::Graph& graph, + EdgeDefinitionAction action, + std::string edgeDefinitionName = {} + ); + + Result modifyVertexDefinition( + graph::Graph& graph, + VertexDefinitionAction action, + std::string vertexDefinitionName + ); + + private: + graph::GraphManager _gmngr; + }; +} // namespace arangodb + +#endif // ARANGOD_REST_HANDLER_REST_GRAPH_HANDLER_H diff --git a/arangod/RestHandler/RestRepairHandler.cpp b/arangod/RestHandler/RestRepairHandler.cpp index 9af8f9f284..bfc67fc151 100644 --- a/arangod/RestHandler/RestRepairHandler.cpp +++ b/arangod/RestHandler/RestRepairHandler.cpp @@ -193,6 +193,7 @@ RestStatus RestRepairHandler::repairDistributeShardsLike() { if (ClusterInfo* clusterInfo = ClusterInfo::instance()) { clusterInfo->loadPlan(); } + } return RestStatus::DONE; } diff --git a/arangod/RestHandler/RestVocbaseBaseHandler.cpp b/arangod/RestHandler/RestVocbaseBaseHandler.cpp index e6f8701414..7f48b1ab10 100644 --- a/arangod/RestHandler/RestVocbaseBaseHandler.cpp +++ b/arangod/RestHandler/RestVocbaseBaseHandler.cpp @@ -102,6 +102,12 @@ std::string const RestVocbaseBaseHandler::DOCUMENT_PATH = "/_api/document"; std::string const RestVocbaseBaseHandler::EDGES_PATH = "/_api/edges"; +//////////////////////////////////////////////////////////////////////////////// +/// @brief gharial graph api path +//////////////////////////////////////////////////////////////////////////////// + +std::string const RestVocbaseBaseHandler::GHARIAL_PATH = "/_api/gharial"; + //////////////////////////////////////////////////////////////////////////////// /// @brief endpoint path //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/RestHandler/RestVocbaseBaseHandler.h b/arangod/RestHandler/RestVocbaseBaseHandler.h index f1c0f61a0c..7132199041 100644 --- a/arangod/RestHandler/RestVocbaseBaseHandler.h +++ b/arangod/RestHandler/RestVocbaseBaseHandler.h @@ -103,6 +103,12 @@ class RestVocbaseBaseHandler : public RestBaseHandler { static std::string const EDGES_PATH; + ////////////////////////////////////////////////////////////////////////////// + /// @brief gharial graph api path + ////////////////////////////////////////////////////////////////////////////// + + static std::string const GHARIAL_PATH; + ////////////////////////////////////////////////////////////////////////////// /// @brief endpoint path ////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/V8Server/v8-general-graph.cpp b/arangod/V8Server/v8-general-graph.cpp new file mode 100644 index 0000000000..db1b50f1d4 --- /dev/null +++ b/arangod/V8Server/v8-general-graph.cpp @@ -0,0 +1,770 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Heiko Kernbach +/// @author Copyright 2018, ArangoDB GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +#include "v8-users.h" + +#include "ApplicationFeatures/ApplicationServer.h" +#include "Basics/VelocyPackHelper.h" +#include "GeneralServer/AuthenticationFeature.h" +#include "Graph/Graph.h" +#include "Graph/GraphManager.h" +#include "Graph/GraphOperations.h" +#include "RestServer/DatabaseFeature.h" +#include "Transaction/V8Context.h" +#include "Utils/ExecContext.h" +#include "V8/v8-conv.h" +#include "V8/v8-globals.h" +#include "V8/v8-utils.h" +#include "V8/v8-vpack.h" +#include "V8Server/v8-externals.h" +#include "V8Server/v8-vocbase.h" +#include "V8Server/v8-vocbaseprivate.h" +#include "V8Server/v8-vocindex.h" +#include "VocBase/LogicalCollection.h" + +#include +#include + +using namespace arangodb; +using namespace arangodb::velocypack; +using namespace arangodb::basics; +using namespace arangodb::graph; +using namespace arangodb::rest; + +static void JS_DropGraph(v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 1) { + TRI_V8_THROW_EXCEPTION_USAGE("_drop(graphName, dropCollections)"); + } else if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + std::string graphName = TRI_ObjectToString(args[0]); + if (graphName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + bool dropCollections = false; + if (args.Length() >= 2) { + dropCollections = TRI_ObjectToBoolean(args[1]); + } + + auto& vocbase = GetContextVocBase(isolate); + auto ctx = transaction::V8Context::Create(vocbase, false); + + GraphManager gmngr{vocbase}; + auto graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + OperationResult result = gmngr.removeGraph(*(graph.get()), true, dropCollections); + + VPackBuilder obj; + obj.add(VPackValue(VPackValueType::Object, true)); + obj.add("removed", VPackValue(result.ok())); + obj.close(); + TRI_V8_RETURN(TRI_VPackToV8(isolate, obj.slice())); + TRI_V8_TRY_CATCH_END +} + +static void JS_RenameGraphCollection( + v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 2) { + TRI_V8_THROW_EXCEPTION_USAGE("_renameCollection(oldName, newName)"); + } else if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } else if (!args[1]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + std::string oldName = TRI_ObjectToString(args[0]); + std::string newName = TRI_ObjectToString(args[1]); + if (oldName.empty() || newName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + + auto& vocbase = GetContextVocBase(isolate); + GraphManager gmngr{vocbase}; + bool r = gmngr.renameGraphCollection(oldName, newName); + + TRI_V8_RETURN(r); + + TRI_V8_RETURN_UNDEFINED(); + TRI_V8_TRY_CATCH_END +} + +static void JS_GraphExists(v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 1) { + TRI_V8_THROW_EXCEPTION_USAGE("_exists(graphName)"); + } else if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + std::string graphName = TRI_ObjectToString(args[0]); + if (graphName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + + auto& vocbase = GetContextVocBase(isolate); + // check if graph already exists + GraphManager gmngr{vocbase}; + bool r = gmngr.graphExists(graphName); + + TRI_V8_RETURN(r); + + TRI_V8_TRY_CATCH_END +} + +static void JS_GetGraph(v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 1) { + TRI_V8_THROW_EXCEPTION_USAGE("_graph(graphName)"); + } else if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + std::string graphName = TRI_ObjectToString(args[0]); + if (graphName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + + auto& vocbase = GetContextVocBase(isolate); + + GraphManager gmngr{vocbase}; + auto graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + VPackBuilder result; + result.openObject(); + graph.get()->graphForClient(result); + result.close(); + VPackSlice resSlice = result.slice().get("graph"); + + TRI_V8_RETURN(TRI_VPackToV8(isolate, resSlice)); + TRI_V8_TRY_CATCH_END +} + +static void JS_GetGraphs(v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + auto& vocbase = GetContextVocBase(isolate); + + GraphManager gmngr{vocbase}; + VPackBuilder result; + OperationResult r = gmngr.readGraphs(result, arangodb::aql::PART_DEPENDENT); + + if (r.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(r.errorNumber(), r.errorMessage()); + } + + if (!result.isEmpty()) { + TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice().get("graphs"))); + } + + TRI_V8_RETURN_UNDEFINED(); + TRI_V8_TRY_CATCH_END +} + +static void JS_GetGraphKeys(v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + auto& vocbase = GetContextVocBase(isolate); + + GraphManager gmngr{vocbase}; + VPackBuilder result; + OperationResult r = + gmngr.readGraphKeys(result, arangodb::aql::PART_DEPENDENT); + + if (r.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(r.errorNumber(), r.errorMessage()); + } + + if (!result.isEmpty()) { + TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice().get("graphs"))); + } + + TRI_V8_RETURN_UNDEFINED(); + TRI_V8_TRY_CATCH_END +} + +static void JS_CreateGraph(v8::FunctionCallbackInfo const& args) { + // TODO Needs to return a wrapped Graph! + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 1) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_create(graphName, edgeDefinitions, orphanCollections, options)"); + } else if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + std::string graphName = TRI_ObjectToString(args[0]); + if (graphName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + + VPackBuilder builder; + builder.openObject(); + + builder.add("name", VPackValue(graphName)); + if (args.Length() >= 2 && !args[1]->IsNullOrUndefined()) { + builder.add(VPackValue(StaticStrings::GraphEdgeDefinitions)); + TRI_V8ToVPack(isolate, builder, args[1], true, true); + builder.close(); + } + if (args.Length() >= 3 && !args[2]->IsNullOrUndefined()) { + builder.add(VPackValue(StaticStrings::GraphOrphans)); + TRI_V8ToVPack(isolate, builder, args[2], true, true); + builder.close(); + } + if (args.Length() >= 4 && !args[3]->IsNullOrUndefined()) { + builder.add(VPackValue("options")); + TRI_V8ToVPack(isolate, builder, args[3], true, true); + builder.close(); + } + + builder.close(); + + auto& vocbase = GetContextVocBase(isolate); + + GraphManager gmngr{vocbase}; + OperationResult r = gmngr.createGraph(builder.slice(), false); + + if (r.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(r.errorNumber(), r.errorMessage()); + } + + auto graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + VPackBuilder result; + result.openObject(); + graph.get()->graphForClient(result); + result.close(); + + TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice())); + TRI_V8_TRY_CATCH_END +} + +static void JS_AddEdgeDefinitions( + v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 2) { + TRI_V8_THROW_EXCEPTION_USAGE("_extendEdgeDefinitions(edgeDefinition)"); + } + if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + std::string graphName = TRI_ObjectToString(args[0]); + if (graphName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + + VPackBuilder edgeDefinition; + TRI_V8ToVPack(isolate, edgeDefinition, args[1], false); + + auto& vocbase = GetContextVocBase(isolate); + GraphManager gmngr{vocbase}; + auto graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + GraphOperations gops{*graph.get(), vocbase}; + OperationResult r = gops.addEdgeDefinition(edgeDefinition.slice(), false); + + if (r.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(r.errorNumber(), r.errorMessage()); + } + + graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + VPackBuilder result; + result.openObject(); + graph.get()->graphForClient(result); + result.close(); + + TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice())); + TRI_V8_TRY_CATCH_END +} + +static void JS_EditEdgeDefinitions( + v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 2) { + TRI_V8_THROW_EXCEPTION_USAGE("_editEdgeDefinitions(edgeDefinition)"); + } + if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + std::string graphName = TRI_ObjectToString(args[0]); + if (graphName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + + VPackBuilder edgeDefinition; + TRI_V8ToVPack(isolate, edgeDefinition, args[1], false); + + auto& vocbase = GetContextVocBase(isolate); + GraphManager gmngr{vocbase}; + auto graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + GraphOperations gops{*(graph.get()), vocbase}; + OperationResult r = gops.editEdgeDefinition( + edgeDefinition.slice(), false, + edgeDefinition.slice().get("collection").copyString()); + + if (r.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(r.errorNumber(), r.errorMessage()); + } + + graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + VPackBuilder result; + result.openObject(); + graph.get()->graphForClient(result); + result.close(); + + TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice())); + TRI_V8_TRY_CATCH_END +} + +static void JS_RemoveVertexCollection( + v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 2) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_removeVertexCollection(vertexName, dropCollection)"); + } + if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + if (!args[1]->IsString()) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_removeVertexCollection(vertexName, dropCollection)"); + } + std::string graphName = TRI_ObjectToString(args[0]); + std::string vertexName = TRI_ObjectToString(args[1]); + if (graphName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + if (vertexName.empty()) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_removeVertexCollection(vertexName, dropCollection)"); + } + bool dropCollection = false; + if (args.Length() >= 3) { + dropCollection = TRI_ObjectToBoolean(args[2]); + } + + auto& vocbase = GetContextVocBase(isolate); + GraphManager gmngr{vocbase}; + auto graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + VPackBuilder builder; + builder.openObject(); + builder.add("collection", VPackValue(vertexName)); + builder.close(); + + GraphOperations gops{*(graph.get()), vocbase}; + OperationResult r = gops.eraseOrphanCollection(false, vertexName, dropCollection); + + if (r.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(r.errorNumber(), r.errorMessage()); + } + + graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + VPackBuilder result; + result.openObject(); + graph.get()->graphForClient(result); + result.close(); + + TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice())); + TRI_V8_TRY_CATCH_END +} + +static void JS_AddVertexCollection( + v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 2) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_addVertexCollection(vertexName, createCollection)"); + } + if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + if (!args[1]->IsString()) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_addVertexCollection(vertexName, createCollection)"); + } + std::string graphName = TRI_ObjectToString(args[0]); + std::string vertexName = TRI_ObjectToString(args[1]); + if (graphName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + if (vertexName.empty()) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_addVertexCollection(vertexName, createCollection)"); + } + bool createCollection = true; + if (args.Length() >= 3) { + createCollection = TRI_ObjectToBoolean(args[2]); + } + + auto& vocbase = GetContextVocBase(isolate); + auto ctx = transaction::V8Context::Create(vocbase, false); + + GraphManager gmngr{vocbase}; + auto graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + GraphOperations gops{*(graph.get()), vocbase}; + + VPackBuilder builder; + builder.openObject(); + builder.add("collection", VPackValue(vertexName)); + builder.close(); + + OperationResult r = + gops.addOrphanCollection(builder.slice(), false, createCollection); + + if (r.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(r.errorNumber(), r.errorMessage()); + } + + graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + VPackBuilder result; + result.openObject(); + graph.get()->graphForClient(result); + result.close(); + + TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice())); + TRI_V8_TRY_CATCH_END +} + +static void JS_DropEdgeDefinition( + v8::FunctionCallbackInfo const& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + if (args.Length() < 2) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_deleteEdgeDefinition(edgeCollection, dropCollection)"); + } + if (!args[0]->IsString()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + if (!args[1]->IsString()) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_deleteEdgeDefinition(edgeCollection, dropCollection)"); + } + std::string graphName = TRI_ObjectToString(args[0]); + std::string edgeDefinitionName = TRI_ObjectToString(args[1]); + if (graphName.empty()) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_GRAPH_CREATE_MISSING_NAME); + } + if (edgeDefinitionName.empty()) { + TRI_V8_THROW_EXCEPTION_USAGE( + "_deleteEdgeDefinition(edgeCollection, dropCollection)"); + } + + bool dropCollections = false; + if (args.Length() >= 3) { + dropCollections = TRI_ObjectToBoolean(args[2]); + } + + auto& vocbase = GetContextVocBase(isolate); + + GraphManager gmngr{vocbase}; + auto graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + GraphOperations gops{*(graph.get()), vocbase}; + OperationResult r = + gops.eraseEdgeDefinition(false, edgeDefinitionName, dropCollections); + + if (r.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(r.errorNumber(), r.errorMessage()); + } + + graph = gmngr.lookupGraphByName(graphName); + if (graph.fail()) { + TRI_V8_THROW_EXCEPTION_MESSAGE(graph.errorNumber(), graph.errorMessage()); + } + TRI_ASSERT(graph.get() != nullptr); + + VPackBuilder result; + result.openObject(); + graph.get()->graphForClient(result); + result.close(); + + TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice())); + TRI_V8_TRY_CATCH_END +} + +static void InitV8GeneralGraphClass(v8::Handle context, + TRI_vocbase_t* vocbase, TRI_v8_global_t* v8g, + v8::Isolate* isolate) { + /* FULL API + * _edgeCollections + * _vertexCollections(bool excludeOrphans) + * _EDGES + * _INEDGES + * _OUTEDGES + * _edges + * _vertices + * _fromVertex(edgeId) + * _toVertex(edgeId) + * _getEdgeCollectionByName + * _getVertexCollectionByName + * _neighbors + * _commonNeighbors + * _countCommonNeighbors + * _commonProperties + * _countCommonProperties + * _paths + * _shortestPath + * _distanceTo + * _absoluteEccentricity + * _farness + * _absoluteCloseness + * _eccentricity + * _closeness + * _absoluteBetweenness + * _betweenness + * _radius + * _diameter + * _orphanCollections + * _renameVertexCollection + * _getConnectingEdges + */ + + v8::Handle rt; + v8::Handle ft; + + ft = v8::FunctionTemplate::New(isolate); + ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoGraph")); + + rt = ft->InstanceTemplate(); + rt->SetInternalFieldCount(2); + + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_addVertexCollection"), + JS_AddVertexCollection); + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_deleteEdgeDefinition"), + JS_DropEdgeDefinition); + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_editEdgeDefinitions"), + JS_EditEdgeDefinitions); + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_extendEdgeDefinitions"), + JS_AddEdgeDefinitions); + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_removeVertexCollection"), + JS_RemoveVertexCollection); + + + + v8g->GeneralGraphTempl.Reset(isolate, rt); + ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoGraphCtor")); + TRI_AddGlobalFunctionVocbase( + isolate, TRI_V8_ASCII_STRING(isolate, "ArangoGraphCtor"), + ft->GetFunction(), true); + + // TODO WE DO NOT NEED THIS. Update _create to return a graph object properly + // register the global object + v8::Handle aa = rt->NewInstance(); + if (!aa.IsEmpty()) { + TRI_AddGlobalVariableVocbase( + isolate, TRI_V8_ASCII_STRING(isolate, "ArangoGraph"), aa); + } +} + +#ifdef USE_ENTERPRISE +static void InitV8SmartGraphClass(v8::Handle context, + TRI_vocbase_t* vocbase, TRI_v8_global_t* v8g, + v8::Isolate* isolate) { + v8::Handle rt; + v8::Handle ft; + + ft = v8::FunctionTemplate::New(isolate); + ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoSmartGraph")); + + rt = ft->InstanceTemplate(); + rt->SetInternalFieldCount(2); + + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_addVertexCollection"), + JS_AddVertexCollection); + + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_deleteEdgeDefinition"), + JS_DropEdgeDefinition); + + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_editEdgeDefinitions"), + JS_EditEdgeDefinitions); + + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_extendEdgeDefinitions"), + JS_AddEdgeDefinitions); + + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_removeVertexCollection"), + JS_RemoveVertexCollection); + + v8g->SmartGraphTempl.Reset(isolate, rt); + ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoSmartGraphCtor")); + TRI_AddGlobalFunctionVocbase( + isolate, TRI_V8_ASCII_STRING(isolate, "ArangoSmartGraphCtor"), + ft->GetFunction(), true); + + // register the global object + v8::Handle aa = rt->NewInstance(); + if (!aa.IsEmpty()) { + TRI_AddGlobalVariableVocbase( + isolate, TRI_V8_ASCII_STRING(isolate, "ArangoSmartGraph"), aa); + } +} +#endif + +static void InitV8GeneralGraphModule(v8::Handle context, + TRI_vocbase_t* vocbase, TRI_v8_global_t* v8g, + v8::Isolate* isolate) { + /* These functions still have a JS only implementation + * JS ONLY: + * _edgeDefinitions + * _extendEdgeDefinitions + * _relation + * _registerCompatibilityFunctions + */ + + /* TODO + * _create // SG => Potentially returns SG + * _graph // SG => Potentially returns SG + */ + v8::Handle rt; + v8::Handle ft; + ft = v8::FunctionTemplate::New(isolate); + ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoGeneralGraphModule")); + rt = ft->InstanceTemplate(); + rt->SetInternalFieldCount(0); + + TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "_create"), + JS_CreateGraph); + TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "_drop"), + JS_DropGraph); + TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "_exists"), + JS_GraphExists); + TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "_graph"), + JS_GetGraph); + TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "_list"), + JS_GetGraphKeys); + TRI_AddMethodVocbase( + isolate, rt, TRI_V8_ASCII_STRING(isolate, "_listObjects"), JS_GetGraphs); + TRI_AddMethodVocbase(isolate, rt, + TRI_V8_ASCII_STRING(isolate, "_renameCollection"), + JS_RenameGraphCollection); + + v8g->GeneralGraphModuleTempl.Reset(isolate, rt); + ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoGeneralGraphModuleCtor")); + TRI_AddGlobalFunctionVocbase( + isolate, TRI_V8_ASCII_STRING(isolate, "ArangoGeneralGraphModuleCtor"), + ft->GetFunction(), true); + + // register the global object + v8::Handle aa = rt->NewInstance(); + if (!aa.IsEmpty()) { + TRI_AddGlobalVariableVocbase( + isolate, TRI_V8_ASCII_STRING(isolate, "ArangoGeneralGraphModule"), aa); + } +} + +void TRI_InitV8GeneralGraph(v8::Handle context, + TRI_vocbase_t* vocbase, TRI_v8_global_t* v8g, + v8::Isolate* isolate) { + InitV8GeneralGraphModule(context, vocbase, v8g, isolate); + InitV8GeneralGraphClass(context, vocbase, v8g, isolate); +#ifdef USE_ENTERPRISE + InitV8SmartGraphClass(context, vocbase, v8g, isolate); +#endif + +} diff --git a/arangod/VocBase/Graphs.h b/arangod/V8Server/v8-general-graph.h similarity index 58% rename from arangod/VocBase/Graphs.h rename to arangod/V8Server/v8-general-graph.h index 9255752d62..a96cba4566 100644 --- a/arangod/VocBase/Graphs.h +++ b/arangod/V8Server/v8-general-graph.h @@ -18,32 +18,19 @@ /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// -/// @author Michael Hackstein +/// @author Heiko Kernbach //////////////////////////////////////////////////////////////////////////////// -#ifndef ARANGOD_VOCBASE_GRAPHS_H -#define ARANGOD_VOCBASE_GRAPHS_H 1 +#ifndef ARANGOD_V8_SERVER_V8_GENERAL_GRAPH_H +#define ARANGOD_V8_SERVER_V8_GENERAL_GRAPH_H 1 -#include "VocBase/vocbase.h" +#include -namespace arangodb { -namespace aql { -class Graph; -} +struct TRI_vocbase_t; +struct TRI_v8_global_t; -namespace transaction { -class Context; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief get an instance of Graph by Name. -/// returns nullptr if graph is not existing -/// The caller has to take care for the memory. -//////////////////////////////////////////////////////////////////////////////// - -arangodb::aql::Graph* lookupGraphByName(std::shared_ptr, std::string const& name); - -} // namespace arangodb +void TRI_InitV8GeneralGraph(v8::Handle context, + TRI_vocbase_t* vocbase, TRI_v8_global_t* v8g, + v8::Isolate* isolate); #endif - diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index 5b97f4887b..2e7893a638 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -79,6 +79,7 @@ #include "V8Server/v8-views.h" #include "V8Server/v8-voccursor.h" #include "V8Server/v8-vocindex.h" +#include "V8Server/v8-general-graph.h" #include "VocBase/KeyGenerator.h" #include "VocBase/LogicalCollection.h" #include "VocBase/Methods/Databases.h" @@ -2068,6 +2069,7 @@ void TRI_InitV8VocBridge( TRI_InitV8Collections(context, &vocbase, v8g, isolate, ArangoNS); TRI_InitV8Views(context, &vocbase, v8g, isolate, ArangoNS); TRI_InitV8Users(context, &vocbase, v8g, isolate); + TRI_InitV8GeneralGraph(context, &vocbase, v8g, isolate); TRI_InitV8cursor(context, v8g); diff --git a/arangod/VocBase/Graphs.cpp b/arangod/VocBase/Graphs.cpp deleted file mode 100644 index 763e8d9efb..0000000000 --- a/arangod/VocBase/Graphs.cpp +++ /dev/null @@ -1,86 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// DISCLAIMER -/// -/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany -/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// -/// Copyright holder is ArangoDB GmbH, Cologne, Germany -/// -/// @author Michael Hackstein -//////////////////////////////////////////////////////////////////////////////// - -#include "Graphs.h" - -#include "Aql/Graphs.h" -#include "Basics/StaticStrings.h" -#include "Cluster/ClusterMethods.h" -#include "Utils/OperationOptions.h" -#include "Utils/SingleCollectionTransaction.h" -#include "Transaction/Context.h" -#include -using namespace arangodb; - -#ifndef USE_ENTERPRISE -std::string const GRAPHS = "_graphs"; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief Load a graph from the _graphs collection; local and coordinator way -//////////////////////////////////////////////////////////////////////////////// - -arangodb::aql::Graph* arangodb::lookupGraphByName(std::shared_ptr transactionContext, - std::string const& name) { - SingleCollectionTransaction trx(transactionContext, GRAPHS, AccessMode::Type::READ); - - Result res = trx.begin(); - - if (!res.ok()) { - std::stringstream ss; - ss << "while looking up graph '" << name << "': " << res.errorMessage(); - res.reset(res.errorNumber(), ss.str()); - THROW_ARANGO_EXCEPTION(res); - } - VPackBuilder b; - { - VPackObjectBuilder guard(&b); - b.add(StaticStrings::KeyString, VPackValue(name)); - } - - // Default options are enough here - OperationOptions options; - - OperationResult result = trx.document(GRAPHS, b.slice(), options); - - // Commit or abort. - res = trx.finish(result.result); - - if (result.fail()) { - THROW_ARANGO_EXCEPTION_FORMAT(result.errorNumber(), "while looking up graph '%s'", - name.c_str()); - } - if (res.fail()) { - std::stringstream ss; - ss << "while looking up graph '" << name << "': " << res.errorMessage(); - res.reset(res.errorNumber(), ss.str()); - THROW_ARANGO_EXCEPTION(res); - } - - VPackSlice info = result.slice(); - if (info.isExternal()) { - info = info.resolveExternal(); - } - - return new arangodb::aql::Graph(info); -} -#endif diff --git a/arangod/VocBase/Methods/Collections.cpp b/arangod/VocBase/Methods/Collections.cpp index b50e1aa20e..53415dd478 100644 --- a/arangod/VocBase/Methods/Collections.cpp +++ b/arangod/VocBase/Methods/Collections.cpp @@ -445,7 +445,7 @@ static int RenameGraphCollections(TRI_vocbase_t* vocbase, } StringBuffer buffer(true); - buffer.appendText("require('@arangodb/general-graph')._renameCollection("); + buffer.appendText("require('@arangodb/general-graph-common')._renameCollection("); buffer.appendJsonEncoded(oldName.c_str(), oldName.size()); buffer.appendChar(','); buffer.appendJsonEncoded(newName.c_str(), newName.size()); diff --git a/js/apps/system/_admin/aardvark/APP/frontend/js/views/graphManagementView.js b/js/apps/system/_admin/aardvark/APP/frontend/js/views/graphManagementView.js index bd97c54367..5a3b415b24 100644 --- a/js/apps/system/_admin/aardvark/APP/frontend/js/views/graphManagementView.js +++ b/js/apps/system/_admin/aardvark/APP/frontend/js/views/graphManagementView.js @@ -655,7 +655,7 @@ } else { newCollectionObject.isSmart = true; newCollectionObject.options = { - numberOfShards: $('#new-numberOfShards').val(), + numberOfShards: parseInt($('#new-numberOfShards').val()), smartGraphAttribute: $('#new-smartGraphAttribute').val(), replicationFactor: parseInt($('#new-replicationFactor').val()) }; @@ -664,15 +664,15 @@ if (frontendConfig.isCluster) { if ($('#general-numberOfShards').val().length > 0) { newCollectionObject.options = { - numberOfShards: $('#general-numberOfShards').val() + numberOfShards: parseInt($('#general-numberOfShards').val()) }; } if ($('#general-replicationFactor').val().length > 0) { if (newCollectionObject.options) { - newCollectionObject.options.replicationFactor = $('#general-replicationFactor').val(); + newCollectionObject.options.replicationFactor = parseInt($('#general-replicationFactor').val()); } else { newCollectionObject.options = { - replicationFactor: $('#general-replicationFactor').val() + replicationFactor: parseInt($('#general-replicationFactor').val()) }; } } diff --git a/js/apps/system/_api/gharial/APP/gharial.js b/js/apps/system/_api/gharial/APP/gharial.js deleted file mode 100644 index ae94b0aca7..0000000000 --- a/js/apps/system/_api/gharial/APP/gharial.js +++ /dev/null @@ -1,1014 +0,0 @@ -'use strict'; - -// ////////////////////////////////////////////////////////////////////////////// -// / DISCLAIMER -// / -// / Copyright 2010-2013 triAGENS GmbH, Cologne, Germany -// / Copyright 2016 ArangoDB GmbH, Cologne, Germany -// / -// / Licensed under the Apache License, Version 2.0 (the "License") -// / you may not use this file except in compliance with the License. -// / You may obtain a copy of the License at -// / -// / http://www.apache.org/licenses/LICENSE-2.0 -// / -// / Unless required by applicable law or agreed to in writing, software -// / distributed under the License is distributed on an "AS IS" BASIS, -// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// / See the License for the specific language governing permissions and -// / limitations under the License. -// / -// / Copyright holder is ArangoDB GmbH, Cologne, Germany -// / -// / @author Michael Hackstein -// / @author Alan Plum -// ////////////////////////////////////////////////////////////////////////////// - -const _ = require('lodash'); -const joi = require('joi'); -const db = require('@arangodb').db; -const dd = require('dedent'); -const statuses = require('statuses'); -const httperr = require('http-errors'); -const errors = require('@arangodb').errors; -const cluster = require('@arangodb/cluster'); -const Graph = require('@arangodb/general-graph'); -const createRouter = require('@arangodb/foxx/router'); -const actions = require('@arangodb/actions'); -const isEnterprise = require('internal').isEnterprise(); - -let SmartGraph = {}; -if (isEnterprise) { - SmartGraph = require('@arangodb/smart-graph'); -} - -const NOT_MODIFIED = statuses('not modified'); -const ACCEPTED = statuses('accepted'); -const CREATED = statuses('created'); -const OK = statuses('ok'); - -const router = createRouter(); -module.context.use(router); - -const loadGraph = (name) => { - try { - if (isEnterprise) { - return SmartGraph._graph(name); - } else { - return Graph._graph(name); - } - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } -}; - -router.use((req, res, next) => { - try { - next(); - } catch (e) { - if (e.isArangoError) { - const status = actions.arangoErrorToHttpCode(e.errorNum); - res.throw(status, e.errorMessage, {errorNum: e.errorNum, cause: e}); - } - if (e.statusCode === NOT_MODIFIED) { - res.status(NOT_MODIFIED); - return; - } - throw e; - } -}); - -function collectionRepresentation (collection, showProperties, showCount, showFigures) { - const result = { - id: collection._id, - name: collection.name(), - isSystem: (result.name.charAt(0) === '_'), - status: collection.status(), - type: collection.type() - }; - - if (showProperties) { - const properties = collection.properties(); - result.doCompact = properties.doCompact; - result.isVolatile = properties.isVolatile; - result.journalSize = properties.journalSize; - result.keyOptions = properties.keyOptions; - result.waitForSync = properties.waitForSync; - if (cluster.isCoordinator()) { - result.shardKeys = properties.shardKeys; - result.numberOfShards = properties.numberOfShards; - } - } - - if (showCount) { - result.count = collection.count(); - } - - if (showFigures) { - const figures = collection.figures(); - - if (figures) { - result.figures = figures; - } - } - - return result; -} - -function checkCollection (g, collection) { - if (!g[collection]) { - throw Object.assign( - new httperr.NotFound(errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.message), - {errorNum: errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.code} - ); - } -} - -function setResponse (res, name, body, code, returnOld, returnNew) { - res.status(code); - if (body._rev) { - res.set('etag', body._rev); - } - let result = { error: false, code }; - if (returnOld) { - result.old = body.old; - delete body.old; - } - if (returnNew) { - result.new = body.new; - delete body.new; - } - if (name === undefined) { - // special hack currently used for HTTP DELETE only - for (let att in body) { - result[att] = body[att]; - } - } else { - result[name] = body; - } - res.json(result); -} - -function matchVertexRevision (req, rev) { - if (req.headers['if-none-match']) { - if (rev === req.headers['if-none-match'].replace(/(^["']|["']$)/g, '')) { - throw httperr(NOT_MODIFIED); - } - } - - if (req.headers['if-match']) { - if (rev !== req.headers['if-match'].replace(/(^["']|["']$)/g, '')) { - throw Object.assign( - new httperr.PreconditionFailed('wrong revision'), - {errorNum: errors.ERROR_GRAPH_INVALID_VERTEX.code} - ); - } - } - - if (req.queryParams.rev) { - if (rev !== req.queryParams.rev) { - throw Object.assign( - new httperr.PreconditionFailed('wrong revision'), - {errorNum: errors.ERROR_GRAPH_INVALID_VERTEX.code} - ); - } - } -} - -function graphForClient (g) { - return { - name: g.__name, - edgeDefinitions: g.__edgeDefinitions, - orphanCollections: g._orphanCollections(), - isSmart: g.__isSmart || false, - numberOfShards: g.__numberOfShards || 0, - replicationFactor: g.__replicationFactor || 1, - smartGraphAttribute: g.__smartGraphAttribute || '', - _id: g.__id, - _rev: g.__rev - }; -} - -// PHP clients like to convert booleans to numbers -const phpCompatFlag = joi.alternatives().try( - joi.boolean(), - joi.number().integer() -); - -const graphName = joi.string() - .description('Name of the graph.'); - -const vertexCollectionName = joi.string() - .description('Name of the vertex collection.'); - -const edgeCollectionName = joi.string() - .description('Name of the edge collection.'); - -const dropCollectionFlag = phpCompatFlag - .description('Flag to drop collection as well.'); - -const definitionEdgeCollectionName = joi.string() - .description('Name of the edge collection in the definition.'); - -const waitForSyncFlag = phpCompatFlag - .description('define if the request should wait until synced to disk.'); - -const returnOldFlag = phpCompatFlag - .description('define if the request should wait return the old version of the updated document.'); - -const returnNewFlag = phpCompatFlag - .description('define if the request should wait return the new version of the updated document.'); - -const vertexKey = joi.string() - .description('_key attribute of one specific vertex'); - -const edgeKey = joi.string() - .description('_key attribute of one specific edge.'); - -const keepNullFlag = phpCompatFlag - .description('define if null values should not be deleted.').default(true); - -// Graph Creation - -router.get('/', function (req, res) { - setResponse(res, 'graphs', Graph._listObjects(), OK); -}) - .summary('List graphs') - .description('Creates a list of all available graphs.'); - -router.post('/', function (req, res) { - const waitForSync = Boolean(req.queryParams.waitForSync); - let g; - try { - if (isEnterprise && req.body.isSmart === true) { - const smartGraphAttribute = req.body.options.smartGraphAttribute; - const replicationFactor = req.body.options.replicationFactor; - const numberOfShards = req.body.options.numberOfShards; - g = SmartGraph._create( - req.body.name, - req.body.edgeDefinitions, - req.body.orphanCollections, - {waitForSync, numberOfShards, smartGraphAttribute, replicationFactor} - ); - } else { - if (req.body.options && req.body.options.numberOfShards && cluster.isCluster()) { - const numberOfShards = req.body.options.numberOfShards || 1; - const replicationFactor = req.body.options.replicationFactor || 1; - g = Graph._create( - req.body.name, - req.body.edgeDefinitions, - req.body.orphanCollections, - {waitForSync, numberOfShards, replicationFactor} - ); - } else if (req.body.options && req.body.options.replicationFactor && cluster.isCluster()) { - const numberOfShards = req.body.options.numberOfShards || 1; - const replicationFactor = req.body.options.replicationFactor || 1; - g = Graph._create( - req.body.name, - req.body.edgeDefinitions, - req.body.orphanCollections, - {waitForSync, numberOfShards, replicationFactor} - ); - } else { - g = Graph._create( - req.body.name, - req.body.edgeDefinitions, - req.body.orphanCollections, - {waitForSync} - ); - } - } - } catch (e) { - if (e.isArangoError) { - switch (e.errorNum) { - case errors.ERROR_BAD_PARAMETER.code: - case errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code: - case errors.ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX.code: - case errors.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.code: - case errors.ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS.code: - case errors.ERROR_ARANGO_DUPLICATE_NAME.code: - require('console').error("Message:", e.errorMessage); - throw Object.assign( - new httperr.BadRequest(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - - case errors.ERROR_GRAPH_DUPLICATE.code: - throw Object.assign( - new httperr.Conflict(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - default: - } - } - throw e; - } - setResponse(res, 'graph', graphForClient(g), waitForSync ? CREATED : ACCEPTED); -}) - .queryParam('waitForSync', waitForSyncFlag) - .body(joi.object({ - name: joi.string().required(), - edgeDefinitions: joi.array().optional(), - orphanCollections: joi.array().optional(), - isSmart: joi.boolean().optional(), - options: joi.object({ - smartGraphAttribute: joi.string().optional(), - replicationFactor: joi.number().integer().greater(0).optional(), - numberOfShards: joi.number().integer().greater(0).optional() - }).optional() - }).required(), 'The required information for a graph') - .error('bad request', 'Graph creation error.') - .error('conflict', 'Graph creation error.') - .summary('Creates a new graph') - .description('Creates a new graph object'); - -router.get('/:graph', function (req, res) { - const name = req.pathParams.graph; - const g = loadGraph(name); - setResponse(res, 'graph', graphForClient(g), OK); -}) - .pathParam('graph', graphName) - .error('not found', 'Graph could not be found.') - .summary('Get information of a graph') - .description(dd` - Selects information for a given graph. - Will return the edge definitions as well as the vertex collections. - Or throws a 404 if the graph does not exist. -`); - -router.delete('/:graph', function (req, res) { - const dropCollections = Boolean(req.queryParams.dropCollections); - const name = req.pathParams.graph; - try { - Graph._drop(name, dropCollections); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - setResponse(res, 'removed', true, ACCEPTED); -}) - .pathParam('graph', graphName) - .queryParam('dropCollections', dropCollectionFlag) - .error('not found', 'The graph does not exist.') - .summary('Drops an existing graph') - .description(dd` - Drops an existing graph object by name. - Optionally all collections not used by other graphs - can be dropped as well. -`); - -// Definitions - -router.get('/:graph/vertex', function (req, res) { - const name = req.pathParams.graph; - const excludeOrphans = req.queryParams.excludeOrphans; - const g = loadGraph(name); - const mapFunc = ( - req.pathParams.collectionObjects - ? (c) => collectionRepresentation(c, false, false, false) - : (c) => c.name() - ); - setResponse(res, 'collections', _.map(g._vertexCollections(excludeOrphans), mapFunc).sort(), OK); -}) - .pathParam('graph', graphName) - .error('not found', 'The graph could not be found.') - .summary('List all vertex collections.') - .description('Gets the list of all vertex collections.'); - -router.post('/:graph/vertex', function (req, res) { - const name = req.pathParams.graph; - const g = loadGraph(name); - try { - g._addVertexCollection(req.body.collection); - } catch (e) { - if (e.isArangoError) { - switch (e.errorNum) { - case errors.ERROR_BAD_PARAMETER.code: - case errors.ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX.code: - case errors.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.code: - case errors.ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS.code: - throw Object.assign( - new httperr.BadRequest(e.errorMessage), - {errorNum: e.errorNum, cause: e}); - - case errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code: - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e}); - } - } - throw e; - } - setResponse(res, 'graph', graphForClient(g), ACCEPTED); -}) - .pathParam('graph', graphName) - .body(joi.object({ - collection: joi.any().required() - }).required(), 'The vertex collection to be stored.') - .error('bad request', 'The vertex collection is invalid.') - .error('not found', 'The graph could not be found.') - .summary('Create a new vertex collection.') - .description('Stores a new vertex collection. This has to contain the vertex-collection name.'); - -router.delete('/:graph/vertex/:collection', function (req, res) { - const dropCollection = Boolean(req.queryParams.dropCollection); - const name = req.pathParams.graph; - const defName = req.pathParams.collection; - const g = loadGraph(name); - try { - g._removeVertexCollection(defName, dropCollection); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_IN_ORPHAN_COLLECTION.code) { - throw Object.assign( - new httperr.BadRequest(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - setResponse(res, 'graph', graphForClient(g), ACCEPTED); -}) - .pathParam('graph', graphName) - .pathParam('collection', vertexCollectionName) - .queryParam('dropCollection', dropCollectionFlag) - .error('bad request', 'The collection is not found or part of an edge definition.') - .error('not found', 'The graph could not be found.') - .summary('Delete a vertex collection.') - .description('Removes a vertex collection from this graph. If this collection is used in one or more edge definitions'); - -router.get('/:graph/edge', function (req, res) { - const name = req.pathParams.graph; - const g = loadGraph(name); - setResponse(res, 'collections', _.map(g._edgeCollections(), (c) => c.name()).sort(), OK); -}) - .pathParam('graph', graphName) - .error('not found', 'The graph could not be found.') - .summary('List all edge collections.') - .description('Get the list of all edge collection.'); - -router.post('/:graph/edge', function (req, res) { - const name = req.pathParams.graph; - const g = loadGraph(name); - try { - g._extendEdgeDefinitions(req.body); - } catch (e) { - if (e.isArangoError) { - switch (e.errorNum) { - case errors.ERROR_GRAPH_COLLECTION_MULTI_USE.code: - case errors.ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS.code: - case errors.ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION.code: - throw Object.assign( - new httperr.BadRequest(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - } - throw e; - } - setResponse(res, 'graph', graphForClient(g), ACCEPTED); -}) - .pathParam('graph', graphName) - .body(joi.any().required(), 'The edge definition to be stored.') - .error('bad request', 'The edge definition is invalid.') - .error('not found', 'The graph could not be found.') - .summary('Create a new edge definition.') - .description(dd` - Stores a new edge definition with the information contained within the body. - This has to contain the edge-collection name, - as well as set of from and to collections-names respectively. -`); - -router.put('/:graph/edge/:definition', function (req, res) { - const name = req.pathParams.graph; - const defName = req.pathParams.definition; - const g = loadGraph(name); - if (defName !== req.body.collection) { - throw Object.assign( - new httperr.NotFound(errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.message), - {errorNum: errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code} - ); - } - try { - g._editEdgeDefinitions(req.body); - } catch (e) { - if (e.isArangoError) { - switch (e.errorNum) { - case errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code: - case errors.ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION.code: - throw Object.assign( - new httperr.BadRequest(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - } - throw e; - } - setResponse(res, 'graph', graphForClient(g), ACCEPTED); -}) - .pathParam('graph', graphName) - .pathParam('definition', definitionEdgeCollectionName) - .body(joi.object().required(), 'The edge definition to be stored.') - .error('bad request', 'The edge definition is invalid.') - .error('not found', 'The graph could not be found.') - .summary('Replace an edge definition.') - .description(dd` - Replaces an existing edge definition with the information contained within the body. - This has to contain the edge-collection name, - as well as set of from and to collections-names respectively. - This will also change the edge definitions of all other graphs using this definition as well. -`); - -router.delete('/:graph/edge/:definition', function (req, res) { - const dropCollection = Boolean(req.queryParams.dropCollection); - const name = req.pathParams.graph; - const defName = req.pathParams.definition; - const g = loadGraph(name); - try { - g._deleteEdgeDefinition(defName, dropCollection); - } catch (e) { - if (e.isArangoError && - errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code === e.errorNum) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - setResponse(res, 'graph', graphForClient(g), ACCEPTED); -}) - .pathParam('graph', graphName) - .pathParam('definition', definitionEdgeCollectionName) - .queryParam('dropCollection', dropCollectionFlag) - .error('not found', 'The graph could not be found.') - .summary('Delete an edge definition.') - .description(dd` - Removes an existing edge definition from this graph. - All data stored in the edge collection are dropped as well - as long as it is not used in other graphs. -`); - -// Vertex Operations - -router.post('/:graph/vertex/:collection', function (req, res) { - const waitForSync = Boolean(req.queryParams.waitForSync); - const returnNew = Boolean(req.queryParams.returnNew); - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const g = loadGraph(name); - checkCollection(g, collection); - let meta; - try { - meta = g[collection].save(req.body, {waitForSync, returnNew}); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_INVALID_EDGE.code) { - throw Object.assign( - new httperr.BadRequest(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - setResponse(res, 'vertex', meta, waitForSync ? CREATED : ACCEPTED, false, returnNew); -}) - .pathParam('graph', graphName) - .pathParam('collection', vertexCollectionName) - .queryParam('waitForSync', waitForSyncFlag) - .queryParam('returnNew', returnNewFlag) - .body(joi.any().required(), 'The document to be stored') - .error('bad request', 'The edge definition is invalid.') - .error('not found', 'Graph or collection not found.') - .summary('Create a new vertex.') - .description('Stores a new vertex with the information contained within the body into the given collection.'); - -router.get('/:graph/vertex/:collection/:key', function (req, res) { - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const key = req.pathParams.key; - const id = `${collection}/${key}`; - const g = loadGraph(name); - checkCollection(g, collection); - let doc; - try { - doc = g[collection].document(id); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - matchVertexRevision(req, doc._rev); - setResponse(res, 'vertex', doc, OK); -}) - .pathParam('graph', graphName) - .pathParam('collection', vertexCollectionName) - .pathParam('key', vertexKey) - .error('not found', 'The vertex does not exist.') - .summary('Get a vertex.') - .description('Gets a vertex with the given key if it is contained within your graph.'); - -router.put('/:graph/vertex/:collection/:key', function (req, res) { - const waitForSync = Boolean(req.queryParams.waitForSync); - const returnOld = Boolean(req.queryParams.returnOld); - const returnNew = Boolean(req.queryParams.returnNew); - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const key = req.pathParams.key; - const id = `${collection}/${key}`; - const g = loadGraph(name); - checkCollection(g, collection); - let doc; - try { - doc = g[collection].document(id); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - matchVertexRevision(req, doc._rev); - const meta = g[collection].replace(id, req.body, {waitForSync, returnOld, returnNew}); - setResponse(res, 'vertex', meta, waitForSync ? OK : ACCEPTED, returnOld, returnNew); -}) - .pathParam('graph', graphName) - .pathParam('collection', vertexCollectionName) - .pathParam('key', vertexKey) - .queryParam('waitForSync', waitForSyncFlag) - .queryParam('returnOld', returnOldFlag) - .queryParam('returnNew', returnNewFlag) - .body(joi.any().required(), 'The document to be stored') - .error('bad request', 'The vertex is invalid.') - .error('not found', 'The vertex does not exist.') - .summary('Replace a vertex.') - .description(dd` - Replaces a vertex with the given id by the content in the body. - This will only run successfully if the vertex is contained within the graph. -`); - -router.patch('/:graph/vertex/:collection/:key', function (req, res) { - const waitForSync = Boolean(req.queryParams.waitForSync); - const keepNull = Boolean(req.queryParams.keepNull); - const returnOld = Boolean(req.queryParams.returnOld); - const returnNew = Boolean(req.queryParams.returnNew); - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const key = req.pathParams.key; - const id = `${collection}/${key}`; - const g = loadGraph(name); - checkCollection(g, collection); - let doc; - try { - doc = g[collection].document(id); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - matchVertexRevision(req, doc._rev); - const meta = g[collection].update(id, req.body, {waitForSync, keepNull, returnOld, returnNew}); - setResponse(res, 'vertex', meta, waitForSync ? OK : ACCEPTED, returnOld, returnNew); -}) - .pathParam('graph', graphName) - .pathParam('collection', vertexCollectionName) - .pathParam('key', vertexKey) - .queryParam('waitForSync', waitForSyncFlag) - .queryParam('returnOld', returnOldFlag) - .queryParam('returnNew', returnNewFlag) - .queryParam('keepNull', keepNullFlag) - .body(joi.any().required(), 'The values that should be modified') - .error('bad request', 'The vertex is invalid.') - .error('not found', 'The vertex does not exist.') - .summary('Update a vertex.') - .description(dd` - Updates a vertex with the given id by adding the content in the body. - This will only run successfully if the vertex is contained within the graph. -`); - -router.delete('/:graph/vertex/:collection/:key', function (req, res) { - const waitForSync = Boolean(req.queryParams.waitForSync); - const returnOld = Boolean(req.queryParams.returnOld); - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const key = req.pathParams.key; - const id = `${collection}/${key}`; - const g = loadGraph(name); - checkCollection(g, collection); - let doc; - try { - doc = g[collection].document(id); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - matchVertexRevision(req, doc._rev); - let didRemove; - try { - didRemove = g[collection].remove(id, {waitForSync}); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - let meta = { removed: didRemove }; - if (returnOld) { - meta.old = doc; - } - setResponse(res, undefined, meta, waitForSync ? OK : ACCEPTED, returnOld, false); -}) - .pathParam('graph', graphName) - .pathParam('collection', vertexCollectionName) - .pathParam('key', vertexKey) - .queryParam('waitForSync', waitForSyncFlag) - .queryParam('returnOld', returnOldFlag) - .error('not found', 'The vertex does not exist.') - .summary('Delete a vertex.') - .description(dd` - Deletes a vertex with the given id, if it is contained within the graph. - Furthermore all edges connected to this vertex will be deleted. -`); - -// Edge Operations - -router.post('/:graph/edge/:collection', function (req, res) { - const waitForSync = Boolean(req.queryParams.waitForSync); - const returnNew = Boolean(req.queryParams.returnNew); - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const g = loadGraph(name); - checkCollection(g, collection); - - if (!req.body._from || !req.body._to) { - throw Object.assign( - new httperr.Gone(errors.ERROR_GRAPH_INVALID_EDGE.message), - {errorNum: errors.ERROR_GRAPH_INVALID_EDGE.code} - ); - } - // check existence of _from and _to vertices - // _from vertex - var found; - try { - found = db._exists({_id: req.body._from}); - if (!found) { - throw Object.assign( - new httperr.NotFound(errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.message), - {errorNum: errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code} - ); - } - } catch (e) { - if (e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.message), - {errorNum: errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, cause: e} - ); - } else { - throw Object.assign( - new httperr.NotFound(errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.message), - {errorNum: errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.code, cause: e} - ); - } - } - - // _to vertex - try { - found = db._exists({_id: req.body._to}); - if (!found) { - throw Object.assign( - new httperr.NotFound(errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.message), - {errorNum: errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code} - ); - } - } catch (e) { - if (e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.message), - {errorNum: errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code, cause: e} - ); - } else { - throw Object.assign( - new httperr.NotFound(errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.message), - {errorNum: errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.code, cause: e} - ); - } - } - - let meta; - try { - meta = g[collection].save(req.body, {waitForSync, returnNew}); - } catch (e) { - if (e.errorNum !== errors.ERROR_GRAPH_INVALID_EDGE.code) { - throw Object.assign( - new httperr.Gone(e.errorMessage), - {errorNum: errors.ERROR_GRAPH_INVALID_EDGE.code, cause: e} - ); - } - throw e; - } - setResponse(res, 'edge', meta, waitForSync ? CREATED : ACCEPTED, false, returnNew); -}) - .pathParam('graph', graphName) - .pathParam('collection', edgeCollectionName) - .queryParam('waitForSync', waitForSyncFlag) - .queryParam('returnNew', returnNewFlag) - .body(joi.object().required(), 'The edge to be stored. Has to contain _from and _to attributes.') - .error('bad request', 'The edge is invalid.') - .error('not found', 'Graph or collection not found.') - .summary('Create a new edge.') - .description('Stores a new edge with the information contained within the body into the given collection.'); - -router.get('/:graph/edge/:collection/:key', function (req, res) { - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const key = req.pathParams.key; - const id = `${collection}/${key}`; - const g = loadGraph(name); - checkCollection(g, collection); - let doc; - try { - doc = g[collection].document(id); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - matchVertexRevision(req, doc._rev); - setResponse(res, 'edge', doc, OK); -}) - .pathParam('graph', graphName) - .pathParam('collection', edgeCollectionName) - .pathParam('key', edgeKey) - .error('not found', 'The edge does not exist.') - .summary('Load an edge.') - .description('Loads an edge with the given id if it is contained within your graph.'); - -router.put('/:graph/edge/:collection/:key', function (req, res) { - const waitForSync = Boolean(req.queryParams.waitForSync); - const returnOld = Boolean(req.queryParams.returnOld); - const returnNew = Boolean(req.queryParams.returnNew); - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const key = req.pathParams.key; - const id = `${collection}/${key}`; - const g = loadGraph(name); - checkCollection(g, collection); - let doc; - try { - doc = g[collection].document(id); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - matchVertexRevision(req, doc._rev); - const meta = g[collection].replace(id, req.body, {waitForSync, returnOld, returnNew}); - setResponse(res, 'edge', meta, waitForSync ? OK : ACCEPTED, returnOld, returnNew); -}) - .pathParam('graph', graphName) - .pathParam('collection', edgeCollectionName) - .pathParam('key', edgeKey) - .queryParam('waitForSync', waitForSyncFlag) - .queryParam('returnOld', returnOldFlag) - .queryParam('returnNew', returnNewFlag) - .body(joi.any().required(), 'The document to be stored. _from and _to attributes are ignored') - .error('bad request', 'The edge is invalid.') - .error('not found', 'The edge does not exist.') - .summary('Replace an edge.') - .description(dd` - Replaces an edge with the given id by the content in the body. - This will only run successfully if the edge is contained within the graph. -`); - -router.patch('/:graph/edge/:collection/:key', function (req, res) { - const waitForSync = Boolean(req.queryParams.waitForSync); - const returnOld = Boolean(req.queryParams.returnOld); - const returnNew = Boolean(req.queryParams.returnNew); - const keepNull = Boolean(req.queryParams.keepNull); - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const key = req.pathParams.key; - const id = `${collection}/${key}`; - const g = loadGraph(name); - checkCollection(g, collection); - let doc; - try { - doc = g[collection].document(id); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - matchVertexRevision(req, doc._rev); - const meta = g[collection].update(id, req.body, {waitForSync, keepNull, returnOld, returnNew}); - setResponse(res, 'edge', meta, waitForSync ? OK : ACCEPTED, returnOld, returnNew); -}) - .pathParam('graph', graphName) - .pathParam('collection', edgeCollectionName) - .pathParam('key', edgeKey) - .queryParam('waitForSync', waitForSyncFlag) - .queryParam('returnOld', returnOldFlag) - .queryParam('returnNew', returnNewFlag) - .queryParam('keepNull', keepNullFlag) - .body(joi.any().required(), 'The values that should be modified. _from and _to attributes are ignored') - .error('bad request', 'The edge is invalid.') - .error('not found', 'The edge does not exist.') - .summary('Update an edge.') - .description(dd` - Updates an edge with the given id by adding the content in the body. - This will only run successfully if the edge is contained within the graph. -`); - -router.delete('/:graph/edge/:collection/:key', function (req, res) { - const waitForSync = Boolean(req.queryParams.waitForSync); - const returnOld = Boolean(req.queryParams.returnOld); - const name = req.pathParams.graph; - const collection = req.pathParams.collection; - const key = req.pathParams.key; - const id = `${collection}/${key}`; - const g = loadGraph(name); - checkCollection(g, collection); - let doc; - try { - doc = g[collection].document(id); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - matchVertexRevision(req, doc._rev); - let didRemove; - try { - didRemove = g[collection].remove(id, {waitForSync}); - } catch (e) { - if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { - throw Object.assign( - new httperr.NotFound(e.errorMessage), - {errorNum: e.errorNum, cause: e} - ); - } - throw e; - } - let meta = { removed: didRemove }; - if (returnOld) { - meta.old = doc; - } - setResponse(res, undefined, meta, waitForSync ? OK : ACCEPTED, returnOld, false); -}) - .pathParam('graph', graphName) - .pathParam('collection', edgeCollectionName) - .pathParam('key', edgeKey) - .queryParam('waitForSync', waitForSyncFlag) - .queryParam('returnOld', returnOldFlag) - .error('not found', 'The edge does not exist.') - .summary('Delete an edge.') - .description('Deletes an edge with the given id, if it is contained within the graph.'); diff --git a/js/apps/system/_api/gharial/APP/manifest.json b/js/apps/system/_api/gharial/APP/manifest.json deleted file mode 100644 index 1346de9c14..0000000000 --- a/js/apps/system/_api/gharial/APP/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "gharial", - "description": "ArangoDB Graph Module", - "author": "ArangoDB GmbH", - "version": "3.0.0", - "license": "Apache-2.0", - - "engines": { - "arangodb": "^3.0.0-0 || ^3.0.0" - }, - - "repository": { - "type": "git", - "url": "https://github.com/arangodb/arangodb.git" - }, - - "contributors": [ - { - "name": "Michael Hackstein", - "email": "m.hackstein@arangodb.com" - } - ], - - "main": "gharial.js" -} diff --git a/js/client/modules/@arangodb/general-graph.js b/js/client/modules/@arangodb/general-graph.js new file mode 100644 index 0000000000..12d7143c3f --- /dev/null +++ b/js/client/modules/@arangodb/general-graph.js @@ -0,0 +1,205 @@ +'use strict'; + +// ////////////////////////////////////////////////////////////////////////////// +// / @brief Replication management +// / +// / @file +// / +// / DISCLAIMER +// / +// / Copyright 2012 triagens 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 Heiko Kernbach +// / @author Copyright 2018, ArangoDB GmbH, Cologne, Germany +// ////////////////////////////////////////////////////////////////////////////// + +const internal = require('internal'); +const arangosh = require('@arangodb/arangosh'); +const ggc = require('@arangodb/general-graph-common'); +const _ = require('lodash'); + +const db = internal.db; + +const GRAPH_PREFIX = '_api/gharial/'; + +// remove me later +exports._exists = ggc._exists; + +// TODO There are several db._flushCache() calls here, whenever gharial might +// have added or dropped collections. Maybe this should be called even when +// the request has failed (in which case now it isn't, because an exception is +// thrown first). + +exports._listObjects = function () { + const uri = GRAPH_PREFIX; + const requestResult = arangosh.checkRequestResult(db._connection.GET(uri)); + return requestResult.graphs; +}; + +exports._list = function () { + const uri = GRAPH_PREFIX; + const requestResult = arangosh.checkRequestResult(db._connection.GET(uri)); + const graphs = requestResult.graphs; + + const result = []; + _.each(graphs, function (graph) { + result.push(graph._key); + }); + return result; +}; + +// inherited graph class +const CommonGraph = ggc.__GraphClass; + +CommonGraph.prototype.__updateDefinitions = function (edgeDefs, orphans) { + this.__edgeDefinitions = edgeDefs; + this.__orphanCollections = orphans; +}; + +CommonGraph.prototype._extendEdgeDefinitions = function (edgeDefinition) { + const data = edgeDefinition || {}; + const uri = GRAPH_PREFIX + encodeURIComponent(this.__name) + "/edge"; + const requestResult = arangosh.checkRequestResult(db._connection.POST(uri, JSON.stringify(data))); + const graph = requestResult.graph; + try { + this.__updateDefinitions(graph.edgeDefinitions, graph.orphanCollections); + } catch (ignore) { + } +}; + +CommonGraph.prototype._editEdgeDefinitions = function (edgeDefinition) { + const data = edgeDefinition || {}; + const uri = GRAPH_PREFIX + encodeURIComponent(this.__name) + "/edge/" + edgeDefinition.collection; + const requestResult = arangosh.checkRequestResult(db._connection.PUT(uri, JSON.stringify(data))); + const graph = requestResult.graph; + try { + this.__updateDefinitions(graph.edgeDefinitions, graph.orphanCollections); + } catch (ignore) { + } +}; + +CommonGraph.prototype._addVertexCollection = function (name, createCollection) { + const data = {}; + if (name) { + data.collection = name; + } + let uri = GRAPH_PREFIX + encodeURIComponent(this.__name) + "/vertex"; + if (createCollection !== false) { + uri += "?createCollection=true"; + } else { + uri += "?createCollection=false"; + } + const requestResult = arangosh.checkRequestResult(db._connection.POST(uri, JSON.stringify(data))); + const graph = requestResult.graph; + + try { + this.__updateDefinitions(graph.edgeDefinitions, graph.orphanCollections); + } catch (ignore) { + } + + if (createCollection !== false) { + db._flushCache(); + } +}; + +CommonGraph.prototype._removeVertexCollection = function (name, dropCollection) { + let uri = GRAPH_PREFIX + encodeURIComponent(this.__name) + "/vertex/" + encodeURIComponent(name); + if (dropCollection === true) { + uri += "?dropCollections=true"; + } else { + uri += "?dropCollections=false"; + } + const requestResult = arangosh.checkRequestResult(db._connection.DELETE(uri)); + const graph = requestResult.graph; + + try { + this.__updateDefinitions(graph.edgeDefinitions, graph.orphanCollections); + } catch (ignore) { + } + + if (dropCollection === true) { + db._flushCache(); + } +}; + +CommonGraph.prototype._deleteEdgeDefinition = function (name, dropCollection = false) { + let uri = GRAPH_PREFIX + encodeURIComponent(this.__name) + "/edge/" + encodeURIComponent(name); + if (dropCollection === true) { + uri += "?dropCollections=true"; + } else { + uri += "?dropCollections=false"; + } + + const requestResult = arangosh.checkRequestResult(db._connection.DELETE(uri)); + const graph = requestResult.graph; + + try { + this.__updateDefinitions(graph.edgeDefinitions, graph.orphanCollections); + } catch (ignore) { + } + + if (dropCollection === true) { + db._flushCache(); + } +}; + +exports._graph = function (graphName) { + const uri = GRAPH_PREFIX + encodeURIComponent(graphName); + const requestResult = arangosh.checkRequestResult(db._connection.GET(uri)); + return new CommonGraph(requestResult.graph); +}; + +exports._create = function (name, edgeDefinitions, orphans, options) { + const data = {}; + if (name) { + data.name = name; + } + if (edgeDefinitions) { + data.edgeDefinitions = edgeDefinitions; + } + if (orphans) { + data.orphanCollections = orphans; + } + if (options) { + data.options = options; + } + + const uri = GRAPH_PREFIX; + const requestResult = arangosh.checkRequestResult(db._connection.POST(uri, JSON.stringify(data))); + db._flushCache(); + return new CommonGraph(requestResult.graph); +}; + +exports._drop = function (graphName, dropCollections) { + + let uri = GRAPH_PREFIX + encodeURIComponent(graphName); + if (dropCollections) { + uri += "?dropCollections=true"; + } + const requestResult = arangosh.checkRequestResult(db._connection.DELETE(uri)); + if (dropCollections) { + db._flushCache(); + } + return requestResult.result; +}; + +// js based helper functions +exports.__GraphClass = CommonGraph; +exports._edgeDefinitions = ggc._edgeDefinitions; +exports._extendEdgeDefinitions = ggc._extendEdgeDefinitions; +exports._relation = ggc._relation; +exports._registerCompatibilityFunctions = ggc._registerCompatibilityFunctions; diff --git a/js/client/tests/http/api-gharial-spec.js b/js/client/tests/http/api-gharial-spec.js index 138e883586..1e68a3e045 100644 --- a/js/client/tests/http/api-gharial-spec.js +++ b/js/client/tests/http/api-gharial-spec.js @@ -24,15 +24,17 @@ // / @author Michael Hackstein // ////////////////////////////////////////////////////////////////////////////// -const expect = require('chai').expect; +const chai = require('chai'); +const expect = chai.expect; +chai.Assertion.addProperty('does', function () { return this; }); const arangodb = require('@arangodb'); const request = require('@arangodb/request'); const ERRORS = arangodb.errors; const db = arangodb.db; -const wait = require('internal').wait; -const extend = require('lodash').extend; +const internal = require('internal'); +const wait = internal.wait; describe('_api/gharial', () => { @@ -314,4 +316,296 @@ describe('_api/gharial', () => { expect(db._collection(vName)).to.not.be.null; }); + it('should check if edges can only be created if their _from and _to vertices are existent - should NOT create - invalid from', () => { + const examples = require('@arangodb/graph-examples/example-graph'); + const exampleGraphName = 'knows_graph'; + const vName = 'persons'; + const eName = 'knows'; + expect(db._collection(eName)).to.be.null; // edgec + expect(db._collection(vName)).to.be.null; // vertexc + const g = examples.loadGraph(exampleGraphName); + expect(g).to.not.be.null; + + const edgeDef = { + _from: 'peter', + _to: 'persons/charlie' + }; + let req = request.post(url + '/' + exampleGraphName + '/edge/knows', { + body: JSON.stringify(edgeDef) + }); + expect(req.statusCode).to.equal(400); + expect(req.json.errorNum).to.equal(ERRORS.ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE.code); + + expect(db._collection(eName)).to.not.be.null; + expect(db._collection(vName)).to.not.be.null; + }); + + it('should check if edges can only be created if their _from and _to vertices are existent - should NOT create - invalid to', () => { + const examples = require('@arangodb/graph-examples/example-graph'); + const exampleGraphName = 'knows_graph'; + const vName = 'persons'; + const eName = 'knows'; + expect(db._collection(eName)).to.be.null; // edgec + expect(db._collection(vName)).to.be.null; // vertexc + const g = examples.loadGraph(exampleGraphName); + expect(g).to.not.be.null; + + const edgeDef = { + _from: 'persons/peter', + _to: 'charlie' + }; + let req = request.post(url + '/' + exampleGraphName + '/edge/knows', { + body: JSON.stringify(edgeDef) + }); + expect(req.statusCode).to.equal(400); + expect(req.json.errorNum).to.equal(ERRORS.ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE.code); + + expect(db._collection(eName)).to.not.be.null; + expect(db._collection(vName)).to.not.be.null; + }); + + it('should check if edges can only be created if their _from and _to vertices are existent - should NOT create - invalid from and to attributes', () => { + const examples = require('@arangodb/graph-examples/example-graph'); + const exampleGraphName = 'knows_graph'; + const vName = 'persons'; + const eName = 'knows'; + expect(db._collection(eName)).to.be.null; // edgec + expect(db._collection(vName)).to.be.null; // vertexc + const g = examples.loadGraph(exampleGraphName); + expect(g).to.not.be.null; + + const edgeDef = { + _from: 'peter', + _to: 'charlie' + }; + let req = request.post(url + '/' + exampleGraphName + '/edge/knows', { + body: JSON.stringify(edgeDef) + }); + expect(req.statusCode).to.equal(400); + expect(req.json.errorNum).to.equal(ERRORS.ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE.code); + + expect(db._collection(eName)).to.not.be.null; + expect(db._collection(vName)).to.not.be.null; + }); + + it('should check if edges can only be created if their _from and _to vertices are existent - should NOT create - missing from and to attributes', () => { + const examples = require('@arangodb/graph-examples/example-graph'); + const exampleGraphName = 'knows_graph'; + const vName = 'persons'; + const eName = 'knows'; + expect(db._collection(eName)).to.be.null; // edgec + expect(db._collection(vName)).to.be.null; // vertexc + const g = examples.loadGraph(exampleGraphName); + expect(g).to.not.be.null; + + const edgeDef = { + }; + let req = request.post(url + '/' + exampleGraphName + '/edge/knows', { + body: JSON.stringify(edgeDef) + }); + expect(req.statusCode).to.equal(400); + expect(req.json.errorNum).to.equal(ERRORS.ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE.code); + + expect(db._collection(eName)).to.not.be.null; + expect(db._collection(vName)).to.not.be.null; + }); + + it('should check if incident edges are deleted with a vertex', () => { + const examples = require('@arangodb/graph-examples/example-graph'); + const exampleGraphName = 'knows_graph'; + const vName = 'persons'; + const eName = 'knows'; + // vertices + const alice = 'alice'; + const bob = 'bob'; + const eve = 'eve'; + + expect(db._collection(eName)).to.be.null; + expect(db._collection(vName)).to.be.null; + // load graph + const g = examples.loadGraph(exampleGraphName); + expect(g).to.not.be.null; + expect(db._collection(eName)).to.not.be.null; + expect(db._collection(vName)).to.not.be.null; + + // pre-check that the expected edges are there + expect(db[eName].all().toArray().length).to.equal(5); + + // delete vertex bob + const res = request.delete( + `${url}/${exampleGraphName}/vertex/${vName}/${bob}` + ); + + // check response + expect(res).to.be.an.instanceof(request.Response); + expect(res.body).to.be.a('string'); + const body = JSON.parse(res.body); + // 202 without waitForSync (default) + expect(body).to.eql({ + error: false, + code: 202, + removed: true + }); + + // check that all edges incident to bob were removed as well + expect(db[eName].all().toArray().length).to.equal(1); + + // check that the remaining edge is the expected one + const eveKnowsAlice = db[eName].all().toArray()[0]; + expect(eveKnowsAlice).to.have.all.keys( + ['_key', '_id', '_rev', '_from', '_to', 'vertex'] + ); + expect(eveKnowsAlice).to.include({ + _from: `${vName}/${eve}`, + _to: `${vName}/${alice}`, + vertex: eve + }); + }); + + it('should check that non-graph incident edges are not deleted with a' + + ' vertex', () => { + const examples = require('@arangodb/graph-examples/example-graph'); + const exampleGraphName = 'knows_graph'; + const vName = 'persons'; + const eName = 'knows'; + // vertices + const alice = 'alice'; + const bob = 'bob'; + const charlie = 'charlie'; + const dave = 'dave'; + const eve = 'eve'; + + expect(db._collection(eName)).to.be.null; + expect(db._collection(vName)).to.be.null; + // load graph + const g = examples.loadGraph(exampleGraphName); + expect(g).to.not.be.null; + expect(db._collection(eName)).to.not.be.null; + expect(db._collection(vName)).to.not.be.null; + + const ngEdges = db._create(eColName); + ngEdges.insert({ + _from: `${vName}/${bob}`, + _to: `${vName}/${charlie}`, + name: 'bob->charlie' + }); + ngEdges.insert({ + _from: `${vName}/${dave}`, + _to: `${vName}/${bob}`, + name: 'dave->bob' + }); + + // pre-check that the expected edges are there + expect(db[eName].all().toArray().length).to.equal(5); + + // delete vertex bob + const res = request.delete( + `${url}/${exampleGraphName}/vertex/${vName}/${bob}` + ); + + // check response + expect(res).to.be.an.instanceof(request.Response); + expect(res.body).to.be.a('string'); + const body = JSON.parse(res.body); + // 202 without waitForSync (default) + expect(body).to.eql({ + error: false, + code: 202, + removed: true + }); + + // check that the edges outside of g are still there + let remainingEdges = ngEdges.all().toArray(); + expect(remainingEdges.length).to.equal(2); + expect(remainingEdges.map(x => x.name)) + .to.have.members(['bob->charlie', 'dave->bob']); + }); + + // TODO deleting a vertex via the graph api should probably delete all + // edges that are in any graph's edge collection, not only the "current" + // graph. Decide this and write a test. + + it('should check that edges can be replaced', () => { + const examples = require('@arangodb/graph-examples/example-graph'); + const exampleGraphName = 'knows_graph'; + const vName = 'persons'; + const eName = 'knows'; + expect(db._collection(eName)).to.be.null; // edgec + expect(db._collection(vName)).to.be.null; // vertexc + const g = examples.loadGraph(exampleGraphName); + expect(g).to.not.be.null; + expect(db._collection(eName)).to.not.be.null; + expect(db._collection(vName)).to.not.be.null; + + const e = db.knows.any(); + + const newEdge = Object.assign({}, e); + newEdge.newAttribute = 'new value'; + + const res = request.put( + `${url}/${exampleGraphName}/edge/${eName}/${e._key}`, + {body: JSON.stringify(newEdge)} + ); + + // 202 without waitForSync (default) + expect(res.statusCode).to.equal(202); + expect(res.json.code).to.equal(202); + expect(res.json.error).to.equal(false); + expect(res.json.edge._key).to.equal(e._key); + + expect(db.knows.document(e._key)) + .to.be.an('object') + .that.has.property('newAttribute') + .which.equals('new value'); + }); + + it('should check that edges can NOT be replaced if their _from or _to' + + ' attribute is missing', () => { + const examples = require('@arangodb/graph-examples/example-graph'); + const exampleGraphName = 'knows_graph'; + const vName = 'persons'; + const eName = 'knows'; + expect(db._collection(eName)).to.be.null; // edgec + expect(db._collection(vName)).to.be.null; // vertexc + const g = examples.loadGraph(exampleGraphName); + expect(g).to.not.be.null; + expect(db._collection(eName)).to.not.be.null; + expect(db._collection(vName)).to.not.be.null; + + const e = db.knows.any(); + + let newEdge; + let newEdges = {}; + newEdge = newEdges['_from missing'] = Object.assign({new: "new"}, e); + delete newEdge._from; + newEdge = newEdges['_to missing'] = Object.assign({new: "new"}, e); + delete newEdge._to; + newEdge = newEdges['_from and _to missing'] = Object.assign({new: "new"}, e); + delete newEdge._from; + delete newEdge._to; + + + for (let key in newEdges) { + if (!newEdges.hasOwnProperty(key)) { + continue; + } + const description = key; + const newEdge = newEdges[key]; + + const res = request.put( + `${url}/${exampleGraphName}/edge/${eName}/${e._key}`, + {body: JSON.stringify(newEdge)} + ); + + expect(res.statusCode, description).to.equal(400); + expect(res.json.errorNum, description) + .to.equal(ERRORS.ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE.code); + } + + expect(db.knows.document(e._key)) + .to.be.an('object') + .that.does.not.have.property('new'); + + }); + }); diff --git a/js/common/bootstrap/errors.js b/js/common/bootstrap/errors.js index 1019585566..f5bccd71b6 100644 --- a/js/common/bootstrap/errors.js +++ b/js/common/bootstrap/errors.js @@ -286,6 +286,10 @@ "ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS" : { "code" : 1938, "message" : "collection used in orphans" }, "ERROR_GRAPH_EDGE_COL_DOES_NOT_EXIST" : { "code" : 1939, "message" : "edge collection does not exist or is not part of the graph" }, "ERROR_GRAPH_EMPTY" : { "code" : 1940, "message" : "empty graph" }, + "ERROR_GRAPH_INTERNAL_DATA_CORRUPT" : { "code" : 1941, "message" : "internal graph data corrupt" }, + "ERROR_GRAPH_INTERNAL_EDGE_COLLECTION_ALREADY_SET" : { "code" : 1942, "message" : "edge collection already set" }, + "ERROR_GRAPH_CREATE_MALFORMED_ORPHAN_LIST" : { "code" : 1943, "message" : "malformed orphan list" }, + "ERROR_GRAPH_EDGE_DEFINITION_IS_DOCUMENT" : { "code" : 1944, "message" : "edge definition collection is a document collection" }, "ERROR_SESSION_UNKNOWN" : { "code" : 1950, "message" : "unknown session" }, "ERROR_SESSION_EXPIRED" : { "code" : 1951, "message" : "session expired" }, "SIMPLE_CLIENT_UNKNOWN_ERROR" : { "code" : 2000, "message" : "unknown client error" }, @@ -316,6 +320,7 @@ "ERROR_CANNOT_DROP_SMART_COLLECTION" : { "code" : 4002, "message" : "cannot drop this smart collection" }, "ERROR_KEY_MUST_BE_PREFIXED_WITH_SMART_GRAPH_ATTRIBUTE" : { "code" : 4003, "message" : "in smart vertex collections _key must be prefixed with the value of the smart graph attribute" }, "ERROR_ILLEGAL_SMART_GRAPH_ATTRIBUTE" : { "code" : 4004, "message" : "attribute cannot be used as smart graph attribute" }, + "ERROR_SMART_GRAPH_ATTRIBUTE_MISMATCH" : { "code" : 4005, "message" : "smart graph attribute mismatch" }, "ERROR_CLUSTER_REPAIRS_FAILED" : { "code" : 5000, "message" : "error during cluster repairs" }, "ERROR_CLUSTER_REPAIRS_NOT_ENOUGH_HEALTHY" : { "code" : 5001, "message" : "not enough (healthy) db servers" }, "ERROR_CLUSTER_REPAIRS_REPLICATION_FACTOR_VIOLATED" : { "code" : 5002, "message" : "replication factor violated during cluster repairs" }, diff --git a/js/common/modules/@arangodb/general-graph.js b/js/common/modules/@arangodb/general-graph-common.js similarity index 89% rename from js/common/modules/@arangodb/general-graph.js rename to js/common/modules/@arangodb/general-graph-common.js index af94bf4723..ebe6609335 100644 --- a/js/common/modules/@arangodb/general-graph.js +++ b/js/common/modules/@arangodb/general-graph-common.js @@ -81,17 +81,9 @@ var findOrCreateCollectionByName = function (name, type, noCreate, options) { let res = false; if (col === null && !noCreate) { if (type === ArangoCollection.TYPE_DOCUMENT) { - if (options) { - col = db._create(name, options); - } else { - col = db._create(name); - } + col = db._create(name, options); } else { - if (options) { - col = db._createEdgeCollection(name, options); - } else { - col = db._createEdgeCollection(name); - } + col = db._createEdgeCollection(name, options); } res = true; } else if (!(col instanceof ArangoCollection)) { @@ -320,12 +312,20 @@ var transformExampleToAQL = function (examples, collections, bindVars, varname) // / internal helper to sort a graph's edge definitions // ////////////////////////////////////////////////////////////////////////////// -var sortEdgeDefinition = function (edgeDefinition) { - edgeDefinition.from = edgeDefinition.from.sort(); - edgeDefinition.to = edgeDefinition.to.sort(); +var sortEdgeDefinitionInplace = function (edgeDefinition) { + edgeDefinition.from.sort(); + edgeDefinition.to.sort(); return edgeDefinition; }; +var sortEdgeDefinition = function (edgeDefinition) { + return { + collection: edgeDefinition.collection, + from: edgeDefinition.from.slice().sort(), + to: edgeDefinition.to.slice().sort(), + }; +}; + // ////////////////////////////////////////////////////////////////////////////// // / @brief create a new graph // ////////////////////////////////////////////////////////////////////////////// @@ -731,6 +731,13 @@ var checkIfMayBeDropped = function (colName, graphName, graphs) { return result; }; +const edgeDefinitionsEqual = function (leftEdgeDef, rightEdgeDef) { + leftEdgeDef = sortEdgeDefinition(leftEdgeDef); + rightEdgeDef = sortEdgeDefinition(rightEdgeDef); + const stringify = obj => JSON.stringify(obj, Object.keys(obj).sort()); + return stringify(leftEdgeDef) === stringify(rightEdgeDef); +}; + // @brief Class Graph. Defines a graph in the Database. class Graph { constructor (info) { @@ -762,7 +769,7 @@ class Graph { var self = this; // Create Hidden Properties createHiddenProperty(this, '__useBuiltIn', useBuiltIn); - createHiddenProperty(this, '__name', info._key); + createHiddenProperty(this, '__name', info._key || info.name); createHiddenProperty(this, '__vertexCollections', vertexCollections); createHiddenProperty(this, '__edgeCollections', edgeCollections); createHiddenProperty(this, '__edgeDefinitions', info.edgeDefinitions); @@ -779,7 +786,7 @@ class Graph { // Create Hidden Functions createHiddenProperty(this, '__updateBindCollections', updateBindCollections); - createHiddenProperty(this, '__sortEdgeDefinition', sortEdgeDefinition); + createHiddenProperty(this, '__sortEdgeDefinition', sortEdgeDefinitionInplace); updateBindCollections(self); } @@ -1551,226 +1558,6 @@ class Graph { return result; } -// ////////////////////////////////////////////////////////////////////////////// -// / @brief was docuBlock JSF_general_graph__extendEdgeDefinitions -// ////////////////////////////////////////////////////////////////////////////// - - _extendEdgeDefinitions (edgeDefinition) { - edgeDefinition = sortEdgeDefinition(edgeDefinition); - var self = this; - var err; - // check if edgeCollection not already used - var eC = edgeDefinition.collection; - // ... in same graph - if (this.__edgeCollections[eC] !== undefined) { - err = new ArangoError(); - err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.code; - err.errorMessage = arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.message; - throw err; - } - // in different graph - db._graphs.toArray().forEach( - function (singleGraph) { - var sGEDs = singleGraph.edgeDefinitions; - sGEDs.forEach( - function (sGED) { - var col = sGED.collection; - if (col === eC) { - if (JSON.stringify(sGED) !== JSON.stringify(edgeDefinition)) { - err = new ArangoError(); - err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS.code; - err.errorMessage = col + ' ' + - arangodb.errors.ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS.message; - throw err; - } - } - } - ); - } - ); - - findOrCreateCollectionsByEdgeDefinitions([edgeDefinition]); - - this.__edgeDefinitions.push(edgeDefinition); - db._graphs.update(this.__name, {edgeDefinitions: this.__edgeDefinitions}); - this.__edgeCollections[edgeDefinition.collection] = db[edgeDefinition.collection]; - edgeDefinition.from.forEach( - function (vc) { - self[vc] = db[vc]; - // remove from __orphanCollections - var orphanIndex = self.__orphanCollections.indexOf(vc); - if (orphanIndex !== -1) { - self.__orphanCollections.splice(orphanIndex, 1); - } - // push into __vertexCollections - if (self.__vertexCollections[vc] === undefined) { - self.__vertexCollections[vc] = db[vc]; - } - } - ); - edgeDefinition.to.forEach( - function (vc) { - self[vc] = db[vc]; - // remove from __orphanCollections - var orphanIndex = self.__orphanCollections.indexOf(vc); - if (orphanIndex !== -1) { - self.__orphanCollections.splice(orphanIndex, 1); - } - // push into __vertexCollections - if (self.__vertexCollections[vc] === undefined) { - self.__vertexCollections[vc] = db[vc]; - } - } - ); - updateBindCollections(this); - } - -// ////////////////////////////////////////////////////////////////////////////// -// / @brief was docuBlock JSF_general_graph__editEdgeDefinition -// ////////////////////////////////////////////////////////////////////////////// - - _editEdgeDefinitions (edgeDefinition) { - edgeDefinition = sortEdgeDefinition(edgeDefinition); - var self = this; - - // check, if in graphs edge definition - if (this.__edgeCollections[edgeDefinition.collection] === undefined) { - var err = new ArangoError(); - err.errorNum = arangodb.errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code; - err.errorMessage = arangodb.errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.message; - throw err; - } - - findOrCreateCollectionsByEdgeDefinitions([edgeDefinition]); - - // evaluate collections to add to orphanage - var possibleOrphans = []; - var currentEdgeDefinition; - this.__edgeDefinitions.forEach( - function (ed) { - if (edgeDefinition.collection === ed.collection) { - currentEdgeDefinition = ed; - } - } - ); - - var currentCollections = _.union(currentEdgeDefinition.from, currentEdgeDefinition.to); - var newCollections = _.union(edgeDefinition.from, edgeDefinition.to); - currentCollections.forEach( - function (colName) { - if (newCollections.indexOf(colName) === -1) { - possibleOrphans.push(colName); - } - } - ); - // change definition for ALL graphs - var graphs = exports._listObjects(); - graphs.forEach( - function (graph) { - changeEdgeDefinitionsForGraph(graph, edgeDefinition, newCollections, possibleOrphans, self); - } - ); - updateBindCollections(this); - } - -// ////////////////////////////////////////////////////////////////////////////// -// / @brief was docuBlock JSF_general_graph__deleteEdgeDefinition -// ////////////////////////////////////////////////////////////////////////////// - - _deleteEdgeDefinition (edgeCollection, dropCollection) { - // check, if in graphs edge definition - if (this.__edgeCollections[edgeCollection] === undefined) { - var err = new ArangoError(); - err.errorNum = arangodb.errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code; - err.errorMessage = arangodb.errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.message; - throw err; - } - if (dropCollection) { - checkRWPermission(edgeCollection); - } - - let edgeDefinitions = this.__edgeDefinitions; - let self = this; - let usedVertexCollections = []; - let possibleOrphans = []; - let index; - - edgeDefinitions.forEach( - function (edgeDefinition, idx) { - if (edgeDefinition.collection === edgeCollection) { - index = idx; - possibleOrphans = edgeDefinition.from; - possibleOrphans = _.union(possibleOrphans, edgeDefinition.to); - } else { - usedVertexCollections = _.union(usedVertexCollections, edgeDefinition.from); - usedVertexCollections = _.union(usedVertexCollections, edgeDefinition.to); - } - } - ); - this.__edgeDefinitions.splice(index, 1); - possibleOrphans.forEach( - function (po) { - if (usedVertexCollections.indexOf(po) === -1) { - self.__orphanCollections.push(po); - } - } - ); - - updateBindCollections(this); - let gdb = getGraphCollection(); - gdb.update( - this.__name, - { - orphanCollections: this.__orphanCollections, - edgeDefinitions: this.__edgeDefinitions - } - ); - - if (dropCollection) { - db._drop(edgeCollection); - } - } - -// ////////////////////////////////////////////////////////////////////////////// -// / @brief was docuBlock JSF_general_graph__addVertexCollection -// ////////////////////////////////////////////////////////////////////////////// - - _addVertexCollection (vertexCollectionName, createCollection) { - // check edgeCollection - var ec = db._collection(vertexCollectionName); - var err; - if (ec === null) { - if (createCollection !== false) { - db._create(vertexCollectionName); - } else { - err = new ArangoError(); - err.errorNum = arangodb.errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code; - err.errorMessage = vertexCollectionName + arangodb.errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.message; - throw err; - } - } else if (ec.type() !== 2) { - err = new ArangoError(); - err.errorNum = arangodb.errors.ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX.code; - err.errorMessage = arangodb.errors.ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX.message; - throw err; - } - if (this.__vertexCollections[vertexCollectionName] !== undefined) { - err = new ArangoError(); - err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.code; - err.errorMessage = arangodb.errors.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.message; - throw err; - } - if (_.includes(this.__orphanCollections, vertexCollectionName)) { - err = new ArangoError(); - err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS.code; - err.errorMessage = arangodb.errors.ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS.message; - throw err; - } - this.__orphanCollections.push(vertexCollectionName); - updateBindCollections(this); - db._graphs.update(this.__name, {orphanCollections: this.__orphanCollections}); - } - // ////////////////////////////////////////////////////////////////////////////// // / @brief was docuBlock JSF_general_graph__orphanCollections // ////////////////////////////////////////////////////////////////////////////// @@ -2013,6 +1800,9 @@ exports._create = function (graphName, edgeDefinitions, orphanCollections, optio err.errorMessage = arangodb.errors.ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION.message; throw err; } + + edgeDefinitions = _.cloneDeep(edgeDefinitions); + // check, if a collection is already used in a different edgeDefinition let tmpCollections = []; let tmpEdgeDefinitions = {}; @@ -2039,7 +1829,7 @@ exports._create = function (graphName, edgeDefinitions, orphanCollections, optio (sGED) => { var col = sGED.collection; if (tmpCollections.indexOf(col) !== -1) { - if (JSON.stringify(sGED) !== JSON.stringify(tmpEdgeDefinitions[col])) { + if (!edgeDefinitionsEqual(sGED, tmpEdgeDefinitions[col])) { let err = new ArangoError(); err.errorNum = arangodb.errors.ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS.code; err.errorMessage = col + ' ' + @@ -2076,12 +1866,7 @@ exports._create = function (graphName, edgeDefinitions, orphanCollections, optio } ); - edgeDefinitions.forEach( - (eD, index) => { - var tmp = sortEdgeDefinition(eD); - edgeDefinitions[index] = tmp; - } - ); + edgeDefinitions.forEach(sortEdgeDefinitionInplace); orphanCollections = orphanCollections.sort(); var data = gdb.save({ @@ -2089,7 +1874,7 @@ exports._create = function (graphName, edgeDefinitions, orphanCollections, optio 'edgeDefinitions': edgeDefinitions, '_key': graphName, 'numberOfShards': options.numberOfShards || 1, - 'replicationFactor': options.replicationFactor || 1 + 'replicationFactor': options.replicationFactor || 1, }, options); data.orphanCollections = orphanCollections; data.edgeDefinitions = edgeDefinitions; @@ -2257,82 +2042,82 @@ exports._listObjects = function () { exports._registerCompatibilityFunctions = function () { const aqlfunctions = require('@arangodb/aql/functions'); aqlfunctions.register('arangodb::GRAPH_EDGES', function (graphName, vertexExample, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._edges(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_VERTICES', function (graphName, vertexExample, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._vertices(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_NEIGHBORS', function (graphName, vertexExample, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._neighbors(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_COMMON_NEIGHBORS', function (graphName, vertex1Example, vertex2Example, options1, options2) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._commonNeighbors(vertex1Example, vertex2Example, options1, options2); }, false); aqlfunctions.register('arangodb::GRAPH_COMMON_PROPERTIES', function (graphName, vertex1Example, vertex2Example, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._commonProperties(vertex1Example, vertex2Example, options); }, false); aqlfunctions.register('arangodb::GRAPH_PATHS', function (graphName, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._paths(options); }, false); aqlfunctions.register('arangodb::GRAPH_SHORTEST_PATH', function (graphName, startVertexExample, edgeVertexExample, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._shortestPath(startVertexExample, edgeVertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_DISTANCE_TO', function (graphName, startVertexExample, edgeVertexExample, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._distanceTo(startVertexExample, edgeVertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_ABSOLUTE_ECCENTRICITY', function (graphName, vertexExample, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._absoluteEccentricity(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_ECCENTRICITY', function (graphName, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._eccentricity(options); }, false); aqlfunctions.register('arangodb::GRAPH_ABSOLUTE_CLOSENESS', function (graphName, vertexExample, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._farness(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_CLOSENESS', function (graphName, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._closeness(options); }, false); aqlfunctions.register('arangodb::GRAPH_ABSOLUTE_BETWEENNESS', function (graphName, vertexExample, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._absoluteBetweenness(vertexExample, options); }, false); aqlfunctions.register('arangodb::GRAPH_BETWEENNESS', function (graphName, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._betweenness(options); }, false); aqlfunctions.register('arangodb::GRAPH_RADIUS', function (graphName, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._radius(options); }, false); aqlfunctions.register('arangodb::GRAPH_DIAMETER', function (graphName, options) { - var gm = require('@arangodb/general-graph'); + var gm = require('@arangodb/general-graph-common'); var g = gm._graph(graphName); return g._diameter(options); }, false); diff --git a/js/common/tests/shell/shell-general-graph.js b/js/common/tests/shell/shell-general-graph.js index e3b45b350a..b9f4112253 100644 --- a/js/common/tests/shell/shell-general-graph.js +++ b/js/common/tests/shell/shell-general-graph.js @@ -57,6 +57,8 @@ function GeneralGraphCreationSuite() { var vn3 = "UnitTestVertices3"; var vn4 = "UnitTestVertices4"; var gn = "UnitTestGraph"; + var gn1 = "UnitTestGraph1"; + var gn2 = "UnitTestGraph2"; var edgeDef = graph._edgeDefinitions( graph._relation(rn, [vn1], [vn1]), graph._relation(rn1, @@ -76,6 +78,14 @@ function GeneralGraphCreationSuite() { vc5 = "UnitTestEdgeDefDeleteVertexCol5", vc6 = "UnitTestEdgeDefDeleteVertexCol6"; + var sortEdgeDefinition = function(edgeDefinition) { + return { + collection: edgeDefinition.collection, + from: edgeDefinition.from.slice().sort(), + to: edgeDefinition.to.slice().sort() + } + }; + return { setUp: function() { @@ -111,6 +121,12 @@ function GeneralGraphCreationSuite() { graph._drop(gN2, true); } catch(ignore) { } + if (db._collection("_graphs").exists(gn1)) { + db._collection("_graphs").remove(gn1); + } + if (db._collection("_graphs").exists(gn2)) { + db._collection("_graphs").remove(gn2); + } }, //////////////////////////////////////////////////////////////////////////////// @@ -381,7 +397,6 @@ function GeneralGraphCreationSuite() { ]); }, - test_create : function () { if (db._collection("_graphs").exists(gn)) { db._collection("_graphs").remove(gn); @@ -467,6 +482,64 @@ function GeneralGraphCreationSuite() { } }, + // Graphs may have overlapping edge collections, as long as their + // definitions are equal. + // This is also a regression test for a broken comparison of edge + // definitions, which did rely on the edge definition object's key order + // and thus sometimes reported spurious inequality. + test_createGraphsWithOverlappingEdgeDefinitions: function () { + if (db._collection("_graphs").exists(gn)) { + db._collection("_graphs").remove(gn); + } + // try to provoke differently ordered keys in + // edge definitions that are actually equal. + const edgeDefs1 = [ + { + collection: rn, + from: [vn1], + to: [vn1], + }, + ]; + const edgeDefs2 = [ + { + to: [vn1], + from: [vn1], + collection: rn, + }, + ]; + graph._create(gn1, edgeDefs1); + graph._create(gn2, edgeDefs2); + }, + + // Graphs may have overlapping edge collections, as long as their + // definitions are equal. + // This is also a regression test for a broken comparison of edge + // definitions, which did rely on the edge definition's from and to + // arrays to be ordered. + test_createGraphsWithLargeOverlappingEdgeDefinitions: function () { + if (db._collection("_graphs").exists(gn)) { + db._collection("_graphs").remove(gn); + } + // try to provoke differently ordered from and to arrays in + // edge definitions that are actually equal. + const edgeDefs1 = [ + { + collection: rn, + from: [vn1, vn2, vn3, vn4], + to: [vn2, vn1, vn4, vn3], + }, + ]; + const edgeDefs2 = [ + { + collection: rn, + from: [vn4, vn3, vn2, vn1], + to: [vn3, vn4, vn1, vn2], + }, + ]; + graph._create(gn1, edgeDefs1); + graph._create(gn2, edgeDefs2); + }, + test_get_graph : function () { if (db._collection("_graphs").exists(gn)) { db._collection("_graphs").remove(gn); @@ -582,11 +655,17 @@ function GeneralGraphCreationSuite() { g1._deleteEdgeDefinition(ec1); assertEqual([dr2, dr3], g1.__edgeDefinitions); assertEqual([vc1, vc2], g1._orphanCollections()); + g1 = graph._graph(gN1); // reload + assertEqual([dr2, dr3], g1.__edgeDefinitions); + assertEqual([vc1, vc2], g1._orphanCollections()); assertTrue(db._collection(ec1) !== null); g1._deleteEdgeDefinition(ec2); assertEqual([dr3], g1.__edgeDefinitions); - assertEqual([vc1, vc2, vc3], g1._orphanCollections()); + assertEqual([vc1, vc2, vc3].sort(), g1._orphanCollections().sort()); + g1 = graph._graph(gN1); // reload + assertEqual([dr3], g1.__edgeDefinitions); + assertEqual([vc1, vc2, vc3].sort(), g1._orphanCollections().sort()); assertTrue(db._collection(ec2) !== null); }, @@ -601,14 +680,20 @@ function GeneralGraphCreationSuite() { g1._deleteEdgeDefinition(ec1, true); assertEqual([dr2, dr3], g1.__edgeDefinitions); assertEqual([vc1, vc2], g1._orphanCollections()); + g1 = graph._graph(gN1); // reload + assertEqual([dr2, dr3], g1.__edgeDefinitions); + assertEqual([vc1, vc2], g1._orphanCollections()); assertTrue(db._collection(ec1) === null); g1._deleteEdgeDefinition(ec2, true); assertEqual([dr3], g1.__edgeDefinitions); - assertEqual([vc1, vc2, vc3], g1._orphanCollections()); + assertEqual([vc1, vc2, vc3].sort(), g1._orphanCollections().sort()); + g1 = graph._graph(gN1); // reload + assertEqual([dr3], g1.__edgeDefinitions); + assertEqual([vc1, vc2, vc3].sort(), g1._orphanCollections().sort()); assertTrue(db._collection(ec2) === null); }, - + test_extendEdgeDefinitionFromExistingGraph1: function() { try { @@ -622,10 +707,11 @@ function GeneralGraphCreationSuite() { try { g1._extendEdgeDefinitions(dr2); + fail(); } catch (e) { assertEqual( e.errorMessage, - arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.message + ec1 + " " + arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.message ); } @@ -680,15 +766,29 @@ function GeneralGraphCreationSuite() { g1 = graph._create(gN1, [dr1]); graph._create(gN2, [dr2]); + const loadG1 = () => graph._graph(gN1); + let sortEdgeDefinitions = function (a,b) { + if (a.collection < b.collection) { + return -1; + } + if (a.collection > b.collection) { + return 1; + } + return 0; + }; + assertEqual([dr1], g1.__edgeDefinitions); g1._addVertexCollection(vc3); assertEqual([vc3], g1._orphanCollections()); + assertEqual([vc3], loadG1()._orphanCollections()); g1._extendEdgeDefinitions(dr3); - assertEqual([dr1, dr3], g1.__edgeDefinitions); + assertEqual([dr1, dr3].sort(sortEdgeDefinitions), g1.__edgeDefinitions); + assertEqual([dr1, dr3].sort(sortEdgeDefinitions), loadG1().__edgeDefinitions); assertEqual([], g1._orphanCollections()); + assertEqual([], loadG1()._orphanCollections()); g1._extendEdgeDefinitions(dr2); - assertEqual([dr1, dr3, dr2], g1.__edgeDefinitions); - + assertEqual([dr1, dr3, dr2].sort(sortEdgeDefinitions), g1.__edgeDefinitions); + assertEqual([dr1, dr3, dr2].sort(sortEdgeDefinitions), loadG1().__edgeDefinitions); }, test_extendEdgeDefinitionFromExistingGraph4: function() { @@ -705,15 +805,22 @@ function GeneralGraphCreationSuite() { dr2 = graph._relation(ec2, [vc4, vc3, vc1, vc2], [vc4, vc3, vc1, vc2]), g1 = graph._create(gN1, [dr1]); + const loadG1 = () => graph._graph(gN1); + g1._extendEdgeDefinitions(dr2); + dr1 = sortEdgeDefinition(dr1); + dr2 = sortEdgeDefinition(dr2); assertEqual([dr1, dr2], g1.__edgeDefinitions); - var edgeDefinition = _.find(g1.__edgeDefinitions, {collection: ec2}); + assertEqual([dr1, dr2], loadG1().__edgeDefinitions); + let edgeDefinition = _.find(g1.__edgeDefinitions, {collection: ec2}); + assertEqual(edgeDefinition.from, [vc1, vc2, vc3, vc4]); + assertEqual(edgeDefinition.to, [vc1, vc2, vc3, vc4]); + edgeDefinition = _.find(loadG1().__edgeDefinitions, {collection: ec2}); assertEqual(edgeDefinition.from, [vc1, vc2, vc3, vc4]); assertEqual(edgeDefinition.to, [vc1, vc2, vc3, vc4]); - - }, + test_editEdgeDefinitionFromExistingGraph1: function() { var dr1 = graph._relation(ec1, [vc1], [vc1, vc2]), dr2 = graph._relation(ec2, [vc3], [vc4, vc5]), @@ -732,37 +839,42 @@ function GeneralGraphCreationSuite() { test_editEdgeDefinitionFromExistingGraph2: function() { - var dr1 = graph._relation(ec1, [vc1, vc2], [vc3, vc4]), - dr2 = graph._relation(ec2, [vc1], [vc4]), - dr3 = graph._relation(ec1, [vc5], [vc5]), - g1 = graph._create(gN1, [dr1, dr2]), - g2 = graph._create(gN2, [dr1]); + var dr1 = graph._relation(ec1, _.cloneDeep([vc1, vc2]), _.cloneDeep([vc3, vc4])), + dr2 = graph._relation(ec2, _.cloneDeep([vc1]), _.cloneDeep([vc4])), + dr3 = graph._relation(ec1, _.cloneDeep([vc5]), _.cloneDeep([vc5])), + g1 = graph._create(gN1, _.cloneDeep([dr1, dr2])), + g2 = graph._create(gN2, _.cloneDeep([dr1])); + + g1._editEdgeDefinitions(_.cloneDeep(dr3)); + + g1 = graph._graph(gN1); + g2 = graph._graph(gN2); - g1._editEdgeDefinitions(dr3); assertEqual([dr3, dr2], g1.__edgeDefinitions); assertEqual([dr3], g2.__edgeDefinitions); - g2 = graph._graph(gN2); + assertEqual(g1._orphanCollections().sort(), [vc2, vc3].sort()); assertEqual(g2._orphanCollections().sort(), [vc1, vc2, vc3, vc4].sort()); - }, test_editEdgeDefinitionFromExistingGraph3: function() { - var dr1 = graph._relation(ec1, [vc1], [vc1, vc2]), - dr2 = graph._relation(ec1, [vc3], [vc4, vc5]), - dr3 = graph._relation(ec2, [vc2], [vc2, vc3]), - g1 = graph._create(gN1, [dr1, dr3]), - g2 = graph._create(gN2, [dr1]); + var dr1 = graph._relation(ec1, _.cloneDeep([vc1]), _.cloneDeep([vc1, vc2])), + dr2 = graph._relation(ec1, _.cloneDeep([vc3]), _.cloneDeep([vc4, vc5])), + dr3 = graph._relation(ec2, _.cloneDeep([vc2]), _.cloneDeep([vc2, vc3])), + g1 = graph._create(gN1, _.cloneDeep([dr1, dr3])), + g2 = graph._create(gN2, _.cloneDeep([dr1])); g1._addVertexCollection(vc4); g2._addVertexCollection(vc5); g2._addVertexCollection(vc6); - g1._editEdgeDefinitions(dr2, true); + g1._editEdgeDefinitions(dr2); + + g2 = graph._graph(gN2); + g1 = graph._graph(gN1); assertEqual([dr2, dr3], g1.__edgeDefinitions); assertEqual([dr2], g2.__edgeDefinitions); - g2 = graph._graph(gN2); assertEqual([vc1], g1._orphanCollections()); assertEqual(g2._orphanCollections().sort(), [vc1, vc2, vc6].sort()); @@ -919,14 +1031,17 @@ function EdgesAndVerticesSuite() { var myGraphName = unitTestGraphName + "2"; var myEdgeColName = "unitTestEdgeCollection4711"; var myVertexColName = vc1; + graph._create( myGraphName, graph._edgeDefinitions( graph._relation(myEdgeColName, myVertexColName, myVertexColName) ) ); + graph._drop(myGraphName, true); assertFalse(graph._exists(myGraphName)); + db._flushCache(); assertTrue(db._collection(myVertexColName) !== null); assertTrue(db._collection(myEdgeColName) === null); }, @@ -1013,11 +1128,9 @@ function EdgesAndVerticesSuite() { graph._relation(myED, myVD2, myVD2) ) ); + fail(); } catch (e) { - assertEqual( - e.errorMessage, - arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.message - ); + assertEqual(e.errorNum, arangodb.errors.ERROR_GRAPH_COLLECTION_MULTI_USE.code) } assertFalse(graph._exists(myGraphName)); assertTrue(db._collection(myVD1) === null); @@ -1144,14 +1257,14 @@ function EdgesAndVerticesSuite() { g[ec1].save(vertexId1, vertexId2, {}); fail(); } catch (e) { - assertEqual(e.errorNum, 1906); + assertEqual(e.errorNum, arangodb.errors.ERROR_GRAPH_INVALID_EDGE.code); } try { g[ec1].insert(vertexId1, vertexId2, {}); fail(); } catch (e) { - assertEqual(e.errorNum, 1906); + assertEqual(e.errorNum, arangodb.errors.ERROR_GRAPH_INVALID_EDGE.code); } g[vc1].remove(vertexId1); @@ -1764,9 +1877,10 @@ function OrphanCollectionSuite() { g1._addVertexCollection(vC4, false); } catch (e) { assertEqual(e.errorNum, ERRORS.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code); - assertEqual(e.errorMessage, vC4 + ERRORS.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.message); + assertEqual(e.errorMessage, vC4 + " " + ERRORS.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.message); } assertTrue(db._collection(vC4) === null); + g1 = graph._graph(gN1); assertEqual(g1._orphanCollections(), []); }, @@ -1786,7 +1900,7 @@ function OrphanCollectionSuite() { g1._addVertexCollection(vC1); } catch (e) { assertEqual(e.errorNum, ERRORS.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.code); - assertEqual(e.errorMessage, ERRORS.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.message); + assertEqual(e.errorMessage, vC1 + " " + ERRORS.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.message); } }, @@ -1795,18 +1909,19 @@ function OrphanCollectionSuite() { try { g1._removeVertexCollection(name); } catch (e) { - assertEqual(e.errorNum, ERRORS.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code); - assertEqual(e.errorMessage, ERRORS.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.message); + assertEqual(e.errorNum, ERRORS.ERROR_GRAPH_NOT_IN_ORPHAN_COLLECTION.code); + assertEqual(e.errorMessage, ERRORS.ERROR_GRAPH_NOT_IN_ORPHAN_COLLECTION.message); } }, test_removeVertexCollection2: function() { g1._addVertexCollection(vC4, true); g1._addVertexCollection(vC5, true); - assertEqual(g1._orphanCollections(), [vC4, vC5]); + assertEqual(g1._orphanCollections().sort(), [vC4, vC5].sort()); g1._removeVertexCollection(vC4, false); assertTrue(db._collection(vC4) !== null); - assertEqual(g1._orphanCollections(), [vC5]); + g1 = graph._graph(gN1); + assertEqual(g1._orphanCollections().sort(), [vC5].sort()); try { g1._removeVertexCollection(vC4, true); } catch (e) { @@ -1822,6 +1937,7 @@ function OrphanCollectionSuite() { graph._drop(gN1, true); assertTrue(db._collection(vC3) !== null); graph._drop(gN2, true); + db._flushCache(); assertTrue(db._collection(vC3) === null); }, @@ -1832,6 +1948,7 @@ function OrphanCollectionSuite() { graph._drop(gN2, true); assertTrue(db._collection(vC3) !== null); graph._drop(gN1, true); + db._flushCache(); assertTrue(db._collection(vC3) === null); }, @@ -1843,6 +1960,7 @@ function OrphanCollectionSuite() { graph._drop(gN1, true); assertTrue(db._collection(vC4) !== null); graph._drop(gN2, true); + db._flushCache(); assertTrue(db._collection(vC4) === null); }, diff --git a/js/server/bootstrap/modules/internal.js b/js/server/bootstrap/modules/internal.js index 7e2c615681..4ce9fd7367 100644 --- a/js/server/bootstrap/modules/internal.js +++ b/js/server/bootstrap/modules/internal.js @@ -62,6 +62,27 @@ exports.ArangoUsers = global.ArangoUsers; delete global.ArangoUsers; + + // ////////////////////////////////////////////////////////////////////////////// + // / @brief ArangoGeneralGraphModule + // ////////////////////////////////////////////////////////////////////////////// + + exports.ArangoGeneralGraphModule = global.ArangoGeneralGraphModule; + delete global.ArangoGeneralGraphModule; + + // ////////////////////////////////////////////////////////////////////////////// + // / @brief ArangoGeneralGraphClass + // ////////////////////////////////////////////////////////////////////////////// + + exports.ArangoGraph = global.ArangoGraph; + delete global.ArangoGraph; + + // ////////////////////////////////////////////////////////////////////////////// + // / @brief ArangoSmartGraphClass + // ////////////////////////////////////////////////////////////////////////////// + + exports.ArangoSmartGraph = global.ArangoSmartGraph; + delete global.ArangoSmartGraph; // ////////////////////////////////////////////////////////////////////////////// // / @brief ArangoDatabase diff --git a/js/server/modules/@arangodb/foxx/manager.js b/js/server/modules/@arangodb/foxx/manager.js index 3991ba1f06..531ac52414 100644 --- a/js/server/modules/@arangodb/foxx/manager.js +++ b/js/server/modules/@arangodb/foxx/manager.js @@ -47,8 +47,7 @@ const isZipBuffer = require('@arangodb/util').isZipBuffer; const SYSTEM_SERVICE_MOUNTS = [ '/_admin/aardvark', // Admin interface. - '/_api/foxx', // Foxx management API. - '/_api/gharial' // General_Graph API. + '/_api/foxx' // Foxx management API. ]; const GLOBAL_SERVICE_MAP = new Map(); diff --git a/js/server/modules/@arangodb/general-graph.js b/js/server/modules/@arangodb/general-graph.js new file mode 100644 index 0000000000..7cc75676c8 --- /dev/null +++ b/js/server/modules/@arangodb/general-graph.js @@ -0,0 +1,100 @@ +/* global ArangoGeneralGraph */ +'use strict'; + +// ////////////////////////////////////////////////////////////////////////////// +// / @brief Replication management +// / +// / @file +// / +// / DISCLAIMER +// / +// / Copyright 2012 triagens GmbH, Cologne, Germany +// / +// / Licensed under the Apache License, Version 2.0 (the "License") +// / you may not use this file except in compliance with the License. +// / You may obtain a copy of the License at +// / +// / http://www.apache.org/licenses/LICENSE-2.0 +// / +// / Unless required by applicable law or agreed to in writing, software +// / distributed under the License is distributed on an "AS IS" BASIS, +// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// / See the License for the specific language governing permissions and +// / limitations under the License. +// / +// / Copyright holder is ArangoDB GmbH, Cologne, Germany +// / +// / @author Heiko Kernbach +// / @author Copyright 2018, ArangoDB GmbH, Cologne, Germany +// ////////////////////////////////////////////////////////////////////////////// + + +const internal = require('internal'); // OK: reloadAuth +const ggc = require('@arangodb/general-graph-common'); +const GeneralGraph = internal.ArangoGeneralGraphModule; + +// This is supposed to be a class +const ArangoGraph = internal.ArangoGraph; +//const arangodb = require("@arangodb"); + +// inherited graph class +let CommonGraph = ggc.__GraphClass; + +// new c++ based +CommonGraph.prototype.__updateDefinitions = function (edgeDefs, orphans) { + this.__edgeDefinitions = edgeDefs; + this.__orphanCollections = orphans; +}; + +CommonGraph.prototype._deleteEdgeDefinition = function (edgeDefinition, dropCollection = false) { + let result = ArangoGraph._deleteEdgeDefinition(this.__name, edgeDefinition, dropCollection); + this.__updateDefinitions(result.graph.edgeDefinitions, result.graph.orphanCollections); +}; + +CommonGraph.prototype._extendEdgeDefinitions = function (edgeDefinitions) { + let result = ArangoGraph._extendEdgeDefinitions(this.__name, edgeDefinitions); + this.__updateDefinitions(result.graph.edgeDefinitions, result.graph.orphanCollections); +}; + +CommonGraph.prototype._editEdgeDefinitions = function (edgeDefinitions) { + let result = ArangoGraph._editEdgeDefinitions(this.__name, edgeDefinitions); + this.__updateDefinitions(result.graph.edgeDefinitions, result.graph.orphanCollections); +}; + +CommonGraph.prototype._addVertexCollection = function (vertexName, createCollection = true) { + let result = ArangoGraph._addVertexCollection(this.__name, vertexName, createCollection); + this.__updateDefinitions(result.graph.edgeDefinitions, result.graph.orphanCollections); +}; + +CommonGraph.prototype._removeVertexCollection = function (vertexName, dropCollection = false) { + let result = ArangoGraph._removeVertexCollection(this.__name, vertexName, dropCollection); + this.__updateDefinitions(result.graph.edgeDefinitions, result.graph.orphanCollections); +}; + + +exports._create = function (name, edgeDefinition, orphans, options) { + let g = GeneralGraph._create(name, edgeDefinition, orphans, options); + return new CommonGraph(g.graph); +}; + +exports._drop = GeneralGraph._drop; + +exports._exists = GeneralGraph._exists; + +exports._graph = function (graphName) { + let g = GeneralGraph._graph(graphName); + return new CommonGraph(g); +}; + +exports._list = GeneralGraph._list; + +exports._listObjects = GeneralGraph._listObjects; + +exports._renameCollection = GeneralGraph._renameCollection; + +// js based helper functions +exports.__GraphClass = CommonGraph; +exports._edgeDefinitions = ggc._edgeDefinitions; +exports._extendEdgeDefinitions = ggc._extendEdgeDefinitions; +exports._relation = ggc._relation; +exports._registerCompatibilityFunctions = ggc._registerCompatibilityFunctions; diff --git a/lib/Basics/Result.h b/lib/Basics/Result.h index ef690cfe0a..8020cb7f61 100644 --- a/lib/Basics/Result.h +++ b/lib/Basics/Result.h @@ -30,6 +30,8 @@ class Result { public: Result() : _errorNumber(TRI_ERROR_NO_ERROR) {} + Result(bool avoidCastingErrors) = delete; + Result(int errorNumber) : _errorNumber(errorNumber){ if (errorNumber != TRI_ERROR_NO_ERROR) { diff --git a/lib/Basics/StaticStrings.cpp b/lib/Basics/StaticStrings.cpp index db104ba38e..3e54067f50 100644 --- a/lib/Basics/StaticStrings.cpp +++ b/lib/Basics/StaticStrings.cpp @@ -64,12 +64,18 @@ std::string const StaticStrings::ReplaceExisting("replaceExisting"); std::string const StaticStrings::OverWrite("overwrite"); // replication headers -std::string const StaticStrings::ReplicationHeaderCheckMore("x-arango-replication-checkmore"); -std::string const StaticStrings::ReplicationHeaderLastIncluded("x-arango-replication-lastincluded"); -std::string const StaticStrings::ReplicationHeaderLastScanned("x-arango-replication-lastscanned"); -std::string const StaticStrings::ReplicationHeaderLastTick("x-arango-replication-lasttick"); -std::string const StaticStrings::ReplicationHeaderFromPresent("x-arango-replication-frompresent"); -std::string const StaticStrings::ReplicationHeaderActive("x-arango-replication-active"); +std::string const StaticStrings::ReplicationHeaderCheckMore( + "x-arango-replication-checkmore"); +std::string const StaticStrings::ReplicationHeaderLastIncluded( + "x-arango-replication-lastincluded"); +std::string const StaticStrings::ReplicationHeaderLastScanned( + "x-arango-replication-lastscanned"); +std::string const StaticStrings::ReplicationHeaderLastTick( + "x-arango-replication-lasttick"); +std::string const StaticStrings::ReplicationHeaderFromPresent( + "x-arango-replication-frompresent"); +std::string const StaticStrings::ReplicationHeaderActive( + "x-arango-replication-active"); // database and collection names std::string const StaticStrings::SystemDatabase("_system"); @@ -155,5 +161,30 @@ std::string const StaticStrings::MimeTypeText("text/plain; charset=utf-8"); std::string const StaticStrings::MimeTypeVPack("application/x-velocypack"); std::string const StaticStrings::MultiPartContentType("multipart/form-data"); +// collection attributes +std::string const StaticStrings::NumberOfShards("numberOfShards"); +std::string const StaticStrings::ReplicationFactor("replicationFactor"); +std::string const StaticStrings::DistributeShardsLike("distributeShardsLike"); + +// graph attribute names +std::string const StaticStrings::GraphCollection("_graphs"); +std::string const StaticStrings::GraphIsSmart("isSmart"); +std::string const StaticStrings::GraphFrom("from"); +std::string const StaticStrings::GraphTo("to"); +std::string const StaticStrings::GraphOptions("options"); +std::string const StaticStrings::GraphSmartGraphAttribute( + "smartGraphAttribute"); +std::string const StaticStrings::GraphEdgeDefinitions("edgeDefinitions"); +std::string const StaticStrings::GraphOrphans("orphanCollections"); +std::string const StaticStrings::GraphInitial("initial"); +std::string const StaticStrings::GraphInitialCid("initialCid"); +std::string const StaticStrings::GraphName("name"); + +// rest query parameter +std::string const StaticStrings::GraphDropCollections("dropCollections"); +std::string const StaticStrings::GraphDropCollection("dropCollection"); +std::string const StaticStrings::GraphCreateCollections("createCollections"); +std::string const StaticStrings::GraphCreateCollection("createCollection"); + // misc strings std::string const StaticStrings::LastValue("lastValue"); diff --git a/lib/Basics/StaticStrings.h b/lib/Basics/StaticStrings.h index e86199ef51..a4dbb221b2 100644 --- a/lib/Basics/StaticStrings.h +++ b/lib/Basics/StaticStrings.h @@ -148,6 +148,26 @@ class StaticStrings { static std::string const MimeTypeVPack; static std::string const MultiPartContentType; + // graph attribute names + static std::string const GraphCollection; + static std::string const GraphIsSmart; + static std::string const GraphFrom; + static std::string const GraphTo; + static std::string const GraphOptions; + static std::string const GraphSmartGraphAttribute; + static std::string const NumberOfShards; + static std::string const DistributeShardsLike; + static std::string const ReplicationFactor; + static std::string const GraphDropCollections; + static std::string const GraphDropCollection; + static std::string const GraphCreateCollections; + static std::string const GraphCreateCollection; + static std::string const GraphEdgeDefinitions; + static std::string const GraphOrphans; + static std::string const GraphInitial; + static std::string const GraphInitialCid; + static std::string const GraphName; + // misc strings static std::string const LastValue; }; diff --git a/lib/Basics/errors.dat b/lib/Basics/errors.dat index e6a220496e..e90cb63e4b 100755 --- a/lib/Basics/errors.dat +++ b/lib/Basics/errors.dat @@ -376,6 +376,10 @@ ERROR_GRAPH_INVALID_ID,1937,"Invalid id","Invalid id", ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS,1938,"collection used in orphans","The collection is already used in the orphans of the graph.", ERROR_GRAPH_EDGE_COL_DOES_NOT_EXIST,1939,"edge collection does not exist or is not part of the graph","the specified edge collection does not exist or is not part of the graph.", ERROR_GRAPH_EMPTY,1940,"empty graph","The requested graph has no edge collections." +ERROR_GRAPH_INTERNAL_DATA_CORRUPT,1941,"internal graph data corrupt","The _graphs collection contains invalid data." +ERROR_GRAPH_INTERNAL_EDGE_COLLECTION_ALREADY_SET,1942,"edge collection already set","Tried to add an edge collection which is already defined." +ERROR_GRAPH_CREATE_MALFORMED_ORPHAN_LIST,1943,"malformed orphan list","the orphan list argument is malformed. It has to be an array of strings." +ERROR_GRAPH_EDGE_DEFINITION_IS_DOCUMENT,1944,"edge definition collection is a document collection","the collection used as a relation is existing, but is a document collection, it cannot be used here." ################################################################################ ## Session errors @@ -436,6 +440,7 @@ ERROR_NO_SMART_GRAPH_ATTRIBUTE,4001,"smart graph attribute not given","The given ERROR_CANNOT_DROP_SMART_COLLECTION,4002,"cannot drop this smart collection","This smart collection cannot be dropped, it dictates sharding in the graph." ERROR_KEY_MUST_BE_PREFIXED_WITH_SMART_GRAPH_ATTRIBUTE,4003,"in smart vertex collections _key must be prefixed with the value of the smart graph attribute","In a smart vertex collection _key must be prefixed with the value of the smart graph attribute." ERROR_ILLEGAL_SMART_GRAPH_ATTRIBUTE,4004,"attribute cannot be used as smart graph attribute","The given smartGraph attribute is illegal and connot be used for sharding. All system attributes are forbidden." +ERROR_SMART_GRAPH_ATTRIBUTE_MISMATCH,4005,"smart graph attribute mismatch","The smart graph attribute of the given collection does not match the smart graph attribute of the graph." ################################################################################ ## Cluster repair errors diff --git a/lib/Basics/voc-errors.cpp b/lib/Basics/voc-errors.cpp index b853d49d0d..882e7c3662 100644 --- a/lib/Basics/voc-errors.cpp +++ b/lib/Basics/voc-errors.cpp @@ -285,6 +285,10 @@ void TRI_InitializeErrorMessages() { REG_ERROR(ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS, "collection used in orphans"); REG_ERROR(ERROR_GRAPH_EDGE_COL_DOES_NOT_EXIST, "edge collection does not exist or is not part of the graph"); REG_ERROR(ERROR_GRAPH_EMPTY, "empty graph"); + REG_ERROR(ERROR_GRAPH_INTERNAL_DATA_CORRUPT, "internal graph data corrupt"); + REG_ERROR(ERROR_GRAPH_INTERNAL_EDGE_COLLECTION_ALREADY_SET, "edge collection already set"); + REG_ERROR(ERROR_GRAPH_CREATE_MALFORMED_ORPHAN_LIST, "malformed orphan list"); + REG_ERROR(ERROR_GRAPH_EDGE_DEFINITION_IS_DOCUMENT, "edge definition collection is a document collection"); REG_ERROR(ERROR_SESSION_UNKNOWN, "unknown session"); REG_ERROR(ERROR_SESSION_EXPIRED, "session expired"); REG_ERROR(SIMPLE_CLIENT_UNKNOWN_ERROR, "unknown client error"); @@ -315,6 +319,7 @@ void TRI_InitializeErrorMessages() { REG_ERROR(ERROR_CANNOT_DROP_SMART_COLLECTION, "cannot drop this smart collection"); REG_ERROR(ERROR_KEY_MUST_BE_PREFIXED_WITH_SMART_GRAPH_ATTRIBUTE, "in smart vertex collections _key must be prefixed with the value of the smart graph attribute"); REG_ERROR(ERROR_ILLEGAL_SMART_GRAPH_ATTRIBUTE, "attribute cannot be used as smart graph attribute"); + REG_ERROR(ERROR_SMART_GRAPH_ATTRIBUTE_MISMATCH, "smart graph attribute mismatch"); REG_ERROR(ERROR_CLUSTER_REPAIRS_FAILED, "error during cluster repairs"); REG_ERROR(ERROR_CLUSTER_REPAIRS_NOT_ENOUGH_HEALTHY, "not enough (healthy) db servers"); REG_ERROR(ERROR_CLUSTER_REPAIRS_REPLICATION_FACTOR_VIOLATED, "replication factor violated during cluster repairs"); diff --git a/lib/Basics/voc-errors.h b/lib/Basics/voc-errors.h index ec4b7640a5..d4dbca9bf6 100644 --- a/lib/Basics/voc-errors.h +++ b/lib/Basics/voc-errors.h @@ -1512,6 +1512,27 @@ constexpr int TRI_ERROR_GRAPH_EDGE_COL_DOES_NOT_EXIST /// The requested graph has no edge collections. constexpr int TRI_ERROR_GRAPH_EMPTY = 1940; +/// 1941: ERROR_GRAPH_INTERNAL_DATA_CORRUPT +/// "internal graph data corrupt" +/// The _graphs collection contains invalid data. +constexpr int TRI_ERROR_GRAPH_INTERNAL_DATA_CORRUPT = 1941; + +/// 1942: ERROR_GRAPH_INTERNAL_EDGE_COLLECTION_ALREADY_SET +/// "edge collection already set" +/// Tried to add an edge collection which is already defined. +constexpr int TRI_ERROR_GRAPH_INTERNAL_EDGE_COLLECTION_ALREADY_SET = 1942; + +/// 1943: ERROR_GRAPH_CREATE_MALFORMED_ORPHAN_LIST +/// "malformed orphan list" +/// the orphan list argument is malformed. It has to be an array of strings. +constexpr int TRI_ERROR_GRAPH_CREATE_MALFORMED_ORPHAN_LIST = 1943; + +/// 1944: ERROR_GRAPH_EDGE_DEFINITION_IS_DOCUMENT +/// "edge definition collection is a document collection" +/// the collection used as a relation is existing, but is a document +/// collection, it cannot be used here. +constexpr int TRI_ERROR_GRAPH_EDGE_DEFINITION_IS_DOCUMENT = 1944; + /// 1950: ERROR_SESSION_UNKNOWN /// "unknown session" /// Will be raised when an invalid/unknown session id is passed to the server. @@ -1665,6 +1686,12 @@ constexpr int TRI_ERROR_KEY_MUST_BE_PREFIXED_WITH_SMART_GRAPH_ATTRIBUTE /// All system attributes are forbidden. constexpr int TRI_ERROR_ILLEGAL_SMART_GRAPH_ATTRIBUTE = 4004; +/// 4005: ERROR_SMART_GRAPH_ATTRIBUTE_MISMATCH +/// "smart graph attribute mismatch" +/// The smart graph attribute of the given collection does not match the smart +/// graph attribute of the graph. +constexpr int TRI_ERROR_SMART_GRAPH_ATTRIBUTE_MISMATCH = 4005; + /// 5000: ERROR_CLUSTER_REPAIRS_FAILED /// "error during cluster repairs" /// General error during cluster repairs diff --git a/lib/Rest/CommonDefines.h b/lib/Rest/CommonDefines.h index 52f3c3718a..029b159ce4 100644 --- a/lib/Rest/CommonDefines.h +++ b/lib/Rest/CommonDefines.h @@ -49,6 +49,39 @@ enum class RequestType { ILLEGAL // must be last }; +inline const char* requestToString(RequestType requestType) { + switch (requestType) { + case RequestType::DELETE_REQ: + return "DELETE"; + case RequestType::GET: + return "GET"; + case RequestType::POST: + return "POST"; + case RequestType::PUT: + return "PUT"; + case RequestType::HEAD: + return "HEAD"; + case RequestType::PATCH: + return "PATCH"; + case RequestType::OPTIONS: + return "OPTIONS"; + case RequestType::VSTREAM_CRED: + return "VSTREAM_CRED"; + case RequestType::VSTREAM_REGISTER: + return "VSTREAM_REGISTER"; + case RequestType::VSTREAM_STATUS: + return "VSTREAM_STATUS"; + case RequestType::ILLEGAL: + default: + return "ILLEGAL"; + } +} + +inline std::ostream& operator<<(std::ostream& ostream, + RequestType requestType) { + return ostream << requestToString(requestType); +} + enum class ContentType { CUSTOM, // use Content-Type from _headers JSON, // application/json @@ -122,6 +155,112 @@ enum class ResponseCode { BANDWIDTH_LIMIT_EXCEEDED = 509, NOT_EXTENDED = 510 }; + +inline const char* responseToString(ResponseCode responseCode) { + switch (responseCode) { + case ResponseCode::CONTINUE: + return "100 CONTINUE"; + case ResponseCode::SWITCHING_PROTOCOLS: + return "101 SWITCHING_PROTOCOLS"; + case ResponseCode::PROCESSING: + return "102 PROCESSING"; + case ResponseCode::OK: + return "200 OK"; + case ResponseCode::CREATED: + return "201 CREATED"; + case ResponseCode::ACCEPTED: + return "202 ACCEPTED"; + case ResponseCode::PARTIAL: + return "203 PARTIAL"; + case ResponseCode::NO_CONTENT: + return "204 NO_CONTENT"; + case ResponseCode::RESET_CONTENT: + return "205 RESET_CONTENT"; + case ResponseCode::PARTIAL_CONTENT: + return "206 PARTIAL_CONTENT"; + case ResponseCode::MOVED_PERMANENTLY: + return "301 MOVED_PERMANENTLY"; + case ResponseCode::FOUND: + return "302 FOUND"; + case ResponseCode::SEE_OTHER: + return "303 SEE_OTHER"; + case ResponseCode::NOT_MODIFIED: + return "304 NOT_MODIFIED"; + case ResponseCode::TEMPORARY_REDIRECT: + return "307 TEMPORARY_REDIRECT"; + case ResponseCode::PERMANENT_REDIRECT: + return "308 PERMANENT_REDIRECT"; + case ResponseCode::BAD: + return "400 BAD"; + case ResponseCode::UNAUTHORIZED: + return "401 UNAUTHORIZED"; + case ResponseCode::PAYMENT_REQUIRED: + return "402 PAYMENT_REQUIRED"; + case ResponseCode::FORBIDDEN: + return "403 FORBIDDEN"; + case ResponseCode::NOT_FOUND: + return "404 NOT_FOUND"; + case ResponseCode::METHOD_NOT_ALLOWED: + return "405 METHOD_NOT_ALLOWED"; + case ResponseCode::NOT_ACCEPTABLE: + return "406 NOT_ACCEPTABLE"; + case ResponseCode::REQUEST_TIMEOUT: + return "408 REQUEST_TIMEOUT"; + case ResponseCode::CONFLICT: + return "409 CONFLICT"; + case ResponseCode::GONE: + return "410 GONE"; + case ResponseCode::LENGTH_REQUIRED: + return "411 LENGTH_REQUIRED"; + case ResponseCode::PRECONDITION_FAILED: + return "412 PRECONDITION_FAILED"; + case ResponseCode::REQUEST_ENTITY_TOO_LARGE: + return "413 REQUEST_ENTITY_TOO_LARGE"; + case ResponseCode::REQUEST_URI_TOO_LONG: + return "414 REQUEST_URI_TOO_LONG"; + case ResponseCode::UNSUPPORTED_MEDIA_TYPE: + return "415 UNSUPPORTED_MEDIA_TYPE"; + case ResponseCode::REQUESTED_RANGE_NOT_SATISFIABLE: + return "416 REQUESTED_RANGE_NOT_SATISFIABLE"; + case ResponseCode::EXPECTATION_FAILED: + return "417 EXPECTATION_FAILED"; + case ResponseCode::I_AM_A_TEAPOT: + return "418 I_AM_A_TEAPOT"; + case ResponseCode::UNPROCESSABLE_ENTITY: + return "422 UNPROCESSABLE_ENTITY"; + case ResponseCode::LOCKED: + return "423 LOCKED"; + case ResponseCode::PRECONDITION_REQUIRED: + return "428 PRECONDITION_REQUIRED"; + case ResponseCode::TOO_MANY_REQUESTS: + return "429 TOO_MANY_REQUESTS"; + case ResponseCode::REQUEST_HEADER_FIELDS_TOO_LARGE: + return "431 REQUEST_HEADER_FIELDS_TOO_LARGE"; + case ResponseCode::UNAVAILABLE_FOR_LEGAL_REASONS: + return "451 UNAVAILABLE_FOR_LEGAL_REASONS"; + case ResponseCode::SERVER_ERROR: + return "500 SERVER_ERROR"; + case ResponseCode::NOT_IMPLEMENTED: + return "501 NOT_IMPLEMENTED"; + case ResponseCode::BAD_GATEWAY: + return "502 BAD_GATEWAY"; + case ResponseCode::SERVICE_UNAVAILABLE: + return "503 SERVICE_UNAVAILABLE"; + case ResponseCode::HTTP_VERSION_NOT_SUPPORTED: + return "505 HTTP_VERSION_NOT_SUPPORTED"; + case ResponseCode::BANDWIDTH_LIMIT_EXCEEDED: + return "509 BANDWIDTH_LIMIT_EXCEEDED"; + case ResponseCode::NOT_EXTENDED: + return "510 NOT_EXTENDED"; + default: + return "??? UNEXPECTED"; + } +} + +inline std::ostream& operator<<(std::ostream& ostream, + ResponseCode responseCode) { + return ostream << responseToString(responseCode); +} } } #endif diff --git a/lib/Rest/GeneralResponse.cpp b/lib/Rest/GeneralResponse.cpp index d183132ca5..8f8887be30 100644 --- a/lib/Rest/GeneralResponse.cpp +++ b/lib/Rest/GeneralResponse.cpp @@ -347,7 +347,6 @@ rest::ResponseCode GeneralResponse::responseCode(int code) { case TRI_ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX: case TRI_ERROR_GRAPH_NOT_IN_ORPHAN_COLLECTION: case TRI_ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF: - case TRI_ERROR_GRAPH_EDGE_COLLECTION_NOT_USED: case TRI_ERROR_GRAPH_INVALID_EXAMPLE_ARRAY_OBJECT_STRING: case TRI_ERROR_GRAPH_INVALID_EXAMPLE_ARRAY_OBJECT: case TRI_ERROR_GRAPH_INVALID_NUMBER_OF_ARGUMENTS: @@ -380,6 +379,7 @@ rest::ResponseCode GeneralResponse::responseCode(int code) { case TRI_ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST: case TRI_ERROR_GRAPH_NO_GRAPH_COLLECTION: case TRI_ERROR_QUEUE_UNKNOWN: + case TRI_ERROR_GRAPH_EDGE_COLLECTION_NOT_USED: return ResponseCode::NOT_FOUND; case TRI_ERROR_CLUSTER_SHARD_LEADER_REFUSES_REPLICATION: diff --git a/lib/V8/v8-globals.cpp b/lib/V8/v8-globals.cpp index 0cba055493..b1b16f0a6f 100644 --- a/lib/V8/v8-globals.cpp +++ b/lib/V8/v8-globals.cpp @@ -38,6 +38,11 @@ TRI_v8_global_t::TRI_v8_global_t(v8::Isolate* isolate) VocbaseTempl(), EnvTempl(), UsersTempl(), + GeneralGraphModuleTempl(), + GeneralGraphTempl(), +#ifdef USE_ENTERPRISE + SmartGraphTempl(), +#endif BufferTempl(), diff --git a/lib/V8/v8-globals.h b/lib/V8/v8-globals.h index fdeff1e066..68331401f0 100644 --- a/lib/V8/v8-globals.h +++ b/lib/V8/v8-globals.h @@ -363,6 +363,19 @@ struct TRI_v8_global_t { /// @brief users template v8::Persistent UsersTempl; + /// @brief general graphs module template + v8::Persistent GeneralGraphModuleTempl; + + /// @brief general graph class template + v8::Persistent GeneralGraphTempl; + +#ifdef USE_ENTERPRISE + /// @brief smart graph class template + v8::Persistent SmartGraphTempl; + // there is no smart graph module becuase they are + // identical, just return different graph instances. +#endif + /// @brief Buffer template v8::Persistent BufferTempl; diff --git a/lib/V8/v8-json.cpp b/lib/V8/v8-json.cpp index be58f92168..88999c41dd 100644 --- a/lib/V8/v8-json.cpp +++ b/lib/V8/v8-json.cpp @@ -35,20 +35,300 @@ -#line 39 "lib/V8/v8-json.cpp" - #define YY_INT_ALIGNED short int /* A lexical scanner generated by flex */ + + + + + + + + + #define FLEX_SCANNER #define YY_FLEX_MAJOR_VERSION 2 -#define YY_FLEX_MINOR_VERSION 5 -#define YY_FLEX_SUBMINOR_VERSION 35 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 #if YY_FLEX_SUBMINOR_VERSION > 0 #define FLEX_BETA #endif + + + + + + + + + + + + + +#ifdef yy_create_buffer +#define tri_v8__create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer tri_v8__create_buffer +#endif + + +#ifdef yy_delete_buffer +#define tri_v8__delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer tri_v8__delete_buffer +#endif + + +#ifdef yy_scan_buffer +#define tri_v8__scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer tri_v8__scan_buffer +#endif + + +#ifdef yy_scan_string +#define tri_v8__scan_string_ALREADY_DEFINED +#else +#define yy_scan_string tri_v8__scan_string +#endif + + +#ifdef yy_scan_bytes +#define tri_v8__scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes tri_v8__scan_bytes +#endif + + +#ifdef yy_init_buffer +#define tri_v8__init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer tri_v8__init_buffer +#endif + + +#ifdef yy_flush_buffer +#define tri_v8__flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer tri_v8__flush_buffer +#endif + + +#ifdef yy_load_buffer_state +#define tri_v8__load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state tri_v8__load_buffer_state +#endif + + +#ifdef yy_switch_to_buffer +#define tri_v8__switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer tri_v8__switch_to_buffer +#endif + + +#ifdef yypush_buffer_state +#define tri_v8_push_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state tri_v8_push_buffer_state +#endif + + +#ifdef yypop_buffer_state +#define tri_v8_pop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state tri_v8_pop_buffer_state +#endif + + +#ifdef yyensure_buffer_stack +#define tri_v8_ensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack tri_v8_ensure_buffer_stack +#endif + + +#ifdef yylex +#define tri_v8_lex_ALREADY_DEFINED +#else +#define yylex tri_v8_lex +#endif + + +#ifdef yyrestart +#define tri_v8_restart_ALREADY_DEFINED +#else +#define yyrestart tri_v8_restart +#endif + + +#ifdef yylex_init +#define tri_v8_lex_init_ALREADY_DEFINED +#else +#define yylex_init tri_v8_lex_init +#endif + + +#ifdef yylex_init_extra +#define tri_v8_lex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra tri_v8_lex_init_extra +#endif + + +#ifdef yylex_destroy +#define tri_v8_lex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy tri_v8_lex_destroy +#endif + + +#ifdef yyget_debug +#define tri_v8_get_debug_ALREADY_DEFINED +#else +#define yyget_debug tri_v8_get_debug +#endif + + +#ifdef yyset_debug +#define tri_v8_set_debug_ALREADY_DEFINED +#else +#define yyset_debug tri_v8_set_debug +#endif + + +#ifdef yyget_extra +#define tri_v8_get_extra_ALREADY_DEFINED +#else +#define yyget_extra tri_v8_get_extra +#endif + + +#ifdef yyset_extra +#define tri_v8_set_extra_ALREADY_DEFINED +#else +#define yyset_extra tri_v8_set_extra +#endif + + +#ifdef yyget_in +#define tri_v8_get_in_ALREADY_DEFINED +#else +#define yyget_in tri_v8_get_in +#endif + + +#ifdef yyset_in +#define tri_v8_set_in_ALREADY_DEFINED +#else +#define yyset_in tri_v8_set_in +#endif + + +#ifdef yyget_out +#define tri_v8_get_out_ALREADY_DEFINED +#else +#define yyget_out tri_v8_get_out +#endif + + +#ifdef yyset_out +#define tri_v8_set_out_ALREADY_DEFINED +#else +#define yyset_out tri_v8_set_out +#endif + + +#ifdef yyget_leng +#define tri_v8_get_leng_ALREADY_DEFINED +#else +#define yyget_leng tri_v8_get_leng +#endif + + +#ifdef yyget_text +#define tri_v8_get_text_ALREADY_DEFINED +#else +#define yyget_text tri_v8_get_text +#endif + + +#ifdef yyget_lineno +#define tri_v8_get_lineno_ALREADY_DEFINED +#else +#define yyget_lineno tri_v8_get_lineno +#endif + + +#ifdef yyset_lineno +#define tri_v8_set_lineno_ALREADY_DEFINED +#else +#define yyset_lineno tri_v8_set_lineno +#endif + + + +#ifdef yyget_column +#define tri_v8_get_column_ALREADY_DEFINED +#else +#define yyget_column tri_v8_get_column +#endif + + +#ifdef yyset_column +#define tri_v8_set_column_ALREADY_DEFINED +#else +#define yyset_column tri_v8_set_column +#endif + + + +#ifdef yywrap +#define tri_v8_wrap_ALREADY_DEFINED +#else +#define yywrap tri_v8_wrap +#endif + + + + + + + + +#ifdef yyalloc +#define tri_v8_alloc_ALREADY_DEFINED +#else +#define yyalloc tri_v8_alloc +#endif + + +#ifdef yyrealloc +#define tri_v8_realloc_ALREADY_DEFINED +#else +#define yyrealloc tri_v8_realloc +#endif + + +#ifdef yyfree +#define tri_v8_free_ALREADY_DEFINED +#else +#define yyfree tri_v8_free +#endif + + + + + + + + + /* First, we deal with platform-specific or compiler-specific issues. */ /* begin standard C headers. */ @@ -82,7 +362,6 @@ typedef int16_t flex_int16_t; typedef uint16_t flex_uint16_t; typedef int32_t flex_int32_t; typedef uint32_t flex_uint32_t; -typedef uint64_t flex_uint64_t; #else typedef signed char flex_int8_t; typedef short int flex_int16_t; @@ -90,7 +369,6 @@ typedef int flex_int32_t; typedef unsigned char flex_uint8_t; typedef unsigned short int flex_uint16_t; typedef unsigned int flex_uint32_t; -#endif /* ! C99 */ /* Limits of integral types. */ #ifndef INT8_MIN @@ -121,38 +399,38 @@ typedef unsigned int flex_uint32_t; #define UINT32_MAX (4294967295U) #endif +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + #endif /* ! FLEXINT_H */ -#ifdef __cplusplus -/* The "const" storage-class-modifier is valid. */ -#define YY_USE_CONST +/* begin standard C++ headers. */ -#else /* ! __cplusplus */ - -/* C99 requires __STDC__ to be defined as 1. */ -#if defined (__STDC__) - -#define YY_USE_CONST - -#endif /* defined (__STDC__) */ -#endif /* ! __cplusplus */ - -#ifdef YY_USE_CONST +/* TODO: this is always defined, so inline it */ #define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) #else -#define yyconst +#define yynoreturn #endif /* Returned upon end-of-file. */ #define YY_NULL 0 -/* Promotes a possibly negative, possibly signed char to an unsigned - * integer for use as an array index. If the signed char is negative, - * we want to instead treat it as an 8-bit unsigned char, hence the - * double cast. + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. */ -#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + + + + /* An opaque pointer. */ #ifndef YY_TYPEDEF_YY_SCANNER_T @@ -160,6 +438,22 @@ typedef unsigned int flex_uint32_t; typedef void* yyscan_t; #endif + + + + + + + + + + + + + + + + /* For convenience, these vars (plus the bison vars far below) are macros in the reentrant scanner. */ #define yyin yyg->yyin_r @@ -171,36 +465,56 @@ typedef void* yyscan_t; #define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) #define yy_flex_debug yyg->yy_flex_debug_r + + + + + + + + + + + + /* Enter a start condition. This macro really ought to take a parameter, * but we do it the disgusting crufty way forced on us by the ()-less * definition of BEGIN. */ #define BEGIN yyg->yy_start = 1 + 2 * - /* Translate the current start state into a value that can be later handed * to BEGIN to return to the state. The YYSTATE alias is for lex * compatibility. */ #define YY_START ((yyg->yy_start - 1) / 2) #define YYSTATE YY_START - /* Action number for EOF rule of a given start state. */ #define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) - /* Special action meaning "start processing a new file". */ -#define YY_NEW_FILE tri_v8_restart(yyin ,yyscanner ) - +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) #define YY_END_OF_BUFFER_CHAR 0 + /* Size of default input buffer. */ #ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else #define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ #endif + /* The state buf must be large enough to hold one state per character in the main buffer. */ #define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + + #ifndef YY_TYPEDEF_YY_BUFFER_STATE #define YY_TYPEDEF_YY_BUFFER_STATE typedef struct yy_buffer_state *YY_BUFFER_STATE; @@ -211,11 +525,15 @@ typedef struct yy_buffer_state *YY_BUFFER_STATE; typedef size_t yy_size_t; #endif + + + #define EOB_ACT_CONTINUE_SCAN 0 #define EOB_ACT_END_OF_FILE 1 #define EOB_ACT_LAST_MATCH 2 - + #define YY_LESS_LINENO(n) + #define YY_LINENO_REWIND_TO(ptr) /* Return all but the first "n" matched characters back to the input stream. */ #define yyless(n) \ @@ -230,27 +548,29 @@ typedef size_t yy_size_t; YY_DO_BEFORE_ACTION; /* set up yytext again */ \ } \ while ( 0 ) - #define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + #ifndef YY_STRUCT_YY_BUFFER_STATE #define YY_STRUCT_YY_BUFFER_STATE struct yy_buffer_state { FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ char *yy_buf_pos; /* current position in input buffer */ /* Size of input buffer in bytes, not including room for EOB * characters. */ - yy_size_t yy_buf_size; + int yy_buf_size; /* Number of characters read into yy_ch_buf, not including EOB * characters. */ - yy_size_t yy_n_chars; + int yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to @@ -273,7 +593,8 @@ struct yy_buffer_state int yy_bs_lineno; /**< The line count. */ int yy_bs_column; /**< The column count. */ - + + /* Whether to try to fill the input buffer when we reach the * end of it. */ @@ -290,7 +611,7 @@ struct yy_buffer_state * possible backing-up. * * When we actually see the EOF, we change the status to "new" - * (via tri_v8_restart()), so that the user can continue scanning by + * (via yyrestart()), so that the user can continue scanning by * just pointing yyin at a new input file. */ #define YY_BUFFER_EOF_PENDING 2 @@ -298,6 +619,9 @@ struct yy_buffer_state }; #endif /* !YY_STRUCT_YY_BUFFER_STATE */ + + + /* We provide macros for accessing buffer states in case in the * future we want to put the buffer states in a more general * "scanner state". @@ -307,84 +631,95 @@ struct yy_buffer_state #define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ : NULL) - /* Same as previous macro, but useful when we know that the buffer stack is not * NULL or when we need an lvalue. For internal use only. */ #define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] -void tri_v8_restart (FILE *input_file ,yyscan_t yyscanner ); -void tri_v8__switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); -YY_BUFFER_STATE tri_v8__create_buffer (FILE *file,int size ,yyscan_t yyscanner ); -void tri_v8__delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); -void tri_v8__flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); -void tri_v8_push_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); -void tri_v8_pop_buffer_state (yyscan_t yyscanner ); -static void tri_v8_ensure_buffer_stack (yyscan_t yyscanner ); -static void tri_v8__load_buffer_state (yyscan_t yyscanner ); -static void tri_v8__init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner ); -#define YY_FLUSH_BUFFER tri_v8__flush_buffer(YY_CURRENT_BUFFER ,yyscanner) -YY_BUFFER_STATE tri_v8__scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner ); -YY_BUFFER_STATE tri_v8__scan_string (yyconst char *yy_str ,yyscan_t yyscanner ); -YY_BUFFER_STATE tri_v8__scan_bytes (yyconst char *bytes,yy_size_t len ,yyscan_t yyscanner ); -void *tri_v8_alloc (yy_size_t ,yyscan_t yyscanner ); -void *tri_v8_realloc (void *,yy_size_t ,yyscan_t yyscanner ); -void tri_v8_free (void * ,yyscan_t yyscanner ); +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); -#define yy_new_buffer tri_v8__create_buffer +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); + + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + + +#define yy_new_buffer yy_create_buffer #define yy_set_interactive(is_interactive) \ { \ if ( ! YY_CURRENT_BUFFER ){ \ - tri_v8_ensure_buffer_stack (yyscanner); \ + yyensure_buffer_stack (yyscanner); \ YY_CURRENT_BUFFER_LVALUE = \ - tri_v8__create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ } - #define yy_set_bol(at_bol) \ { \ if ( ! YY_CURRENT_BUFFER ){\ - tri_v8_ensure_buffer_stack (yyscanner); \ + yyensure_buffer_stack (yyscanner); \ YY_CURRENT_BUFFER_LVALUE = \ - tri_v8__create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ } - #define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + /* Begin user sect3 */ -#define tri_v8_wrap(n) 1 +#define tri_v8_wrap(yyscanner) (/*CONSTCOND*/1) #define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; -typedef unsigned char YY_CHAR; typedef int yy_state_type; #define yytext_ptr yytext_r -static yy_state_type yy_get_previous_state (yyscan_t yyscanner ); -static yy_state_type yy_try_NUL_trans (yy_state_type current_state ,yyscan_t yyscanner); -static int yy_get_next_buffer (yyscan_t yyscanner ); -static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner ); + + + + + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + + + /* Done after the current pattern has been matched and before the * corresponding action - sets up yytext. */ #define YY_DO_BEFORE_ACTION \ yyg->yytext_ptr = yy_bp; \ - yyleng = (yy_size_t) (yy_cp - yy_bp); \ + yyleng = (int) (yy_cp - yy_bp); \ yyg->yy_hold_char = *yy_cp; \ *yy_cp = '\0'; \ yyg->yy_c_buf_p = yy_cp; - #define YY_NUM_RULES 15 #define YY_END_OF_BUFFER 16 /* This struct is not used in this scanner, @@ -394,7 +729,7 @@ struct yy_trans_info flex_int32_t yy_verify; flex_int32_t yy_nxt; }; -static yyconst flex_int16_t yy_accept[45] = +static const flex_int16_t yy_accept[45] = { 0, 13, 13, 16, 14, 13, 13, 14, 14, 11, 6, 6, 12, 14, 14, 14, 9, 10, 7, 8, 13, @@ -403,7 +738,7 @@ static yyconst flex_int16_t yy_accept[45] = 2, 3, 1, 0 } ; -static yyconst flex_int32_t yy_ec[256] = +static const YY_CHAR yy_ec[256] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, @@ -435,7 +770,7 @@ static yyconst flex_int32_t yy_ec[256] = 1, 1, 1, 1, 1 } ; -static yyconst flex_int32_t yy_meta[37] = +static const YY_CHAR yy_meta[37] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -443,7 +778,7 @@ static yyconst flex_int32_t yy_meta[37] = 1, 1, 1, 1, 1, 1 } ; -static yyconst flex_int16_t yy_base[47] = +static const flex_int16_t yy_base[47] = { 0, 0, 0, 114, 126, 35, 38, 42, 35, 126, 40, 41, 126, 35, 35, 39, 126, 126, 126, 126, 60, @@ -452,7 +787,7 @@ static yyconst flex_int16_t yy_base[47] = 126, 126, 126, 126, 78, 75 } ; -static yyconst flex_int16_t yy_def[47] = +static const flex_int16_t yy_def[47] = { 0, 44, 1, 44, 44, 44, 44, 45, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, @@ -461,7 +796,7 @@ static yyconst flex_int16_t yy_def[47] = 44, 44, 44, 0, 44, 44 } ; -static yyconst flex_int16_t yy_nxt[163] = +static const flex_int16_t yy_nxt[163] = { 0, 4, 5, 6, 5, 4, 7, 8, 9, 8, 4, 10, 11, 12, 4, 4, 13, 4, 14, 4, 4, @@ -483,7 +818,7 @@ static yyconst flex_int16_t yy_nxt[163] = 44, 44 } ; -static yyconst flex_int16_t yy_chk[163] = +static const flex_int16_t yy_chk[163] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -540,8 +875,13 @@ struct jsonData { } \ while (0) + + #define INITIAL 0 + + + #ifndef YY_NO_UNISTD_H /* Special case for "unistd.h", since it is non-ANSI. We include it way * down here because we want the user's section 1 to have been scanned first. @@ -550,8 +890,13 @@ struct jsonData { #include #endif + + #define YY_EXTRA_TYPE struct jsonData + + + /* Holds the entire state of the reentrant scanner. */ struct yyguts_t { @@ -565,8 +910,8 @@ struct yyguts_t size_t yy_buffer_stack_max; /**< capacity of stack. */ YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ char yy_hold_char; - yy_size_t yy_n_chars; - yy_size_t yyleng_r; + int yy_n_chars; + int yyleng_r; char *yy_c_buf_p; int yy_init; int yy_start; @@ -580,46 +925,106 @@ struct yyguts_t int yylineno_r; int yy_flex_debug_r; + + + char *yytext_r; int yy_more_flag; int yy_more_len; + + + + + }; /* end struct yyguts_t */ -static int yy_init_globals (yyscan_t yyscanner ); -int tri_v8_lex_init (yyscan_t* scanner); -int tri_v8_lex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner); + +static int yy_init_globals ( yyscan_t yyscanner ); + + + + + + + + + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + + /* Accessor methods to globals. These are made visible to non-reentrant scanners for convenience. */ -int tri_v8_lex_destroy (yyscan_t yyscanner ); -int tri_v8_get_debug (yyscan_t yyscanner ); +int yylex_destroy ( yyscan_t yyscanner ); -void tri_v8_set_debug (int debug_flag ,yyscan_t yyscanner ); -YY_EXTRA_TYPE tri_v8_get_extra (yyscan_t yyscanner ); -void tri_v8_set_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner ); +int yyget_debug ( yyscan_t yyscanner ); -FILE *tri_v8_get_in (yyscan_t yyscanner ); -void tri_v8_set_in (FILE * in_str ,yyscan_t yyscanner ); -FILE *tri_v8_get_out (yyscan_t yyscanner ); +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); -void tri_v8_set_out (FILE * out_str ,yyscan_t yyscanner ); -yy_size_t tri_v8_get_leng (yyscan_t yyscanner ); -char *tri_v8_get_text (yyscan_t yyscanner ); +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + + + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + + + +FILE *yyget_in ( yyscan_t yyscanner ); + + + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + + + +FILE *yyget_out ( yyscan_t yyscanner ); + + + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + + + int yyget_leng ( yyscan_t yyscanner ); + + + +char *yyget_text ( yyscan_t yyscanner ); + + + +int yyget_lineno ( yyscan_t yyscanner ); + + + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + + + + +int yyget_column ( yyscan_t yyscanner ); + + + + + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + -int tri_v8_get_lineno (yyscan_t yyscanner ); -void tri_v8_set_lineno (int line_number ,yyscan_t yyscanner ); /* Macros after this point can all be overridden by user definitions in * section 1. @@ -627,43 +1032,62 @@ void tri_v8_set_lineno (int line_number ,yyscan_t yyscanner ); #ifndef YY_SKIP_YYWRAP #ifdef __cplusplus -extern "C" int tri_v8_wrap (yyscan_t yyscanner ); +extern "C" int yywrap ( yyscan_t yyscanner ); #else -extern int tri_v8_wrap (yyscan_t yyscanner ); +extern int yywrap ( yyscan_t yyscanner ); #endif #endif +#ifndef YY_NO_UNPUT + +#endif + + #ifndef yytext_ptr -static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner); +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); #endif #ifdef YY_NEED_STRLEN -static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner); +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); #endif #ifndef YY_NO_INPUT - #ifdef __cplusplus -static int yyinput (yyscan_t yyscanner ); +static int yyinput ( yyscan_t yyscanner ); #else -static int input (yyscan_t yyscanner ); +static int input ( yyscan_t yyscanner ); #endif #endif + + + + + + + /* Amount of stuff to slurp up with each read. */ #ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else #define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ #endif + /* Copy whatever the last rule matched to the standard output. */ #ifndef ECHO /* This used to be an fputs(), but since the string might contain NUL's, * we now use fwrite(). */ -#define ECHO fwrite( yytext, yyleng, 1, yyout ) +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) #endif + + /* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, * is returned in "result". */ @@ -672,7 +1096,7 @@ static int input (yyscan_t yyscanner ); if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ { \ int c = '*'; \ - yy_size_t n; \ + int n; \ for ( n = 0; n < max_size && \ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ buf[n] = (char) c; \ @@ -685,7 +1109,7 @@ static int input (yyscan_t yyscanner ); else \ { \ errno=0; \ - while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ { \ if( errno != EINTR) \ { \ @@ -700,6 +1124,8 @@ static int input (yyscan_t yyscanner ); #endif + + /* No semi-colon after return; correct usage is to write "yyterminate();" - * we don't want an extra ';' after the "return" because that will cause * some compilers to complain about unreachable statements. @@ -708,29 +1134,44 @@ static int input (yyscan_t yyscanner ); #define yyterminate() return YY_NULL #endif + /* Number of entries by which start-condition stack grows. */ #ifndef YY_START_STACK_INCR #define YY_START_STACK_INCR 25 #endif + /* Report a fatal error. */ #ifndef YY_FATAL_ERROR #define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) #endif + + /* end tables serialization structures and prototypes */ + + /* Default declaration of generated scanner - a define so the user can * easily add parameters. */ #ifndef YY_DECL #define YY_DECL_IS_OURS 1 -extern int tri_v8_lex (yyscan_t yyscanner); -#define YY_DECL int tri_v8_lex (yyscan_t yyscanner) + + + + + + + +extern int yylex (yyscan_t yyscanner); + +#define YY_DECL int yylex (yyscan_t yyscanner) #endif /* !YY_DECL */ + /* Code executed at the beginning of each rule, after yytext and yyleng * have been set up. */ @@ -738,14 +1179,19 @@ extern int tri_v8_lex (yyscan_t yyscanner); #define YY_USER_ACTION #endif + + /* Code executed at the end of each rule. */ #ifndef YY_BREAK -#define YY_BREAK break; +#define YY_BREAK /*LINTED*/break; #endif + + #define YY_RULE_SETUP \ YY_USER_ACTION + /** The main scanner function which does all the work. */ YY_DECL @@ -755,9 +1201,11 @@ YY_DECL int yy_act; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; - /* ----------------------------------------------------------------------------- - * keywords - * ----------------------------------------------------------------------------- */ + + + + + if ( !yyg->yy_init ) { @@ -767,6 +1215,8 @@ YY_DECL YY_USER_INIT; #endif + + if ( ! yyg->yy_start ) yyg->yy_start = 1; /* first start state */ @@ -777,15 +1227,23 @@ YY_DECL yyout = stdout; if ( ! YY_CURRENT_BUFFER ) { - tri_v8_ensure_buffer_stack (yyscanner); + yyensure_buffer_stack (yyscanner); YY_CURRENT_BUFFER_LVALUE = - tri_v8__create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); } - tri_v8__load_buffer_state(yyscanner ); + yy_load_buffer_state( yyscanner ); } - while ( 1 ) /* loops until end-of-file is reached */ + { + + + /* ----------------------------------------------------------------------------- + * keywords + * ----------------------------------------------------------------------------- */ + + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ { yy_cp = yyg->yy_c_buf_p; @@ -801,7 +1259,7 @@ YY_DECL yy_match: do { - YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; @@ -811,9 +1269,9 @@ yy_match: { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 45 ) - yy_c = yy_meta[(unsigned int) yy_c]; + yy_c = yy_meta[yy_c]; } - yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; ++yy_cp; } while ( yy_current_state != 44 ); @@ -825,8 +1283,11 @@ yy_find_action: YY_DO_BEFORE_ACTION; + + do_action: /* This label is used only to access EOF actions. */ + switch ( yy_act ) { /* beginning of action switch */ case 0: /* must back up */ @@ -958,7 +1419,7 @@ case YY_STATE_EOF(INITIAL): /* We're scanning a new file or input source. It's * possible that this happened because the user * just pointed yyin at a new source and called - * tri_v8_lex(). If so, then we have to assure + * yylex(). If so, then we have to assure * consistency between YY_CURRENT_BUFFER and our * globals. Here is the right place to do so, because * this is the first action (other than possibly a @@ -1019,7 +1480,7 @@ case YY_STATE_EOF(INITIAL): { yyg->yy_did_buffer_switch_on_eof = 0; - if ( tri_v8_wrap(yyscanner ) ) + if ( yywrap( yyscanner ) ) { /* Note: because we've taken care in * yy_get_next_buffer() to have set up @@ -1072,7 +1533,12 @@ case YY_STATE_EOF(INITIAL): "fatal flex scanner internal error--no action found" ); } /* end of action switch */ } /* end of scanning one token */ -} /* end of tri_v8_lex */ + } /* end of user's declarations */ +} /* end of yylex */ + + + + /* yy_get_next_buffer - try to read in a new buffer * @@ -1115,7 +1581,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner) /* Try to read more data. */ /* First move last chars to start of buffer. */ - number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1; + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); for ( i = 0; i < number_to_move; ++i ) *(dest++) = *(source++); @@ -1128,21 +1594,21 @@ static int yy_get_next_buffer (yyscan_t yyscanner) else { - yy_size_t num_to_read = + int num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; while ( num_to_read <= 0 ) { /* Not enough room in the buffer - grow it. */ /* just a shorter name for the current buffer */ - YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; int yy_c_buf_p_offset = (int) (yyg->yy_c_buf_p - b->yy_ch_buf); if ( b->yy_is_our_buffer ) { - yy_size_t new_size = b->yy_buf_size * 2; + int new_size = b->yy_buf_size * 2; if ( new_size <= 0 ) b->yy_buf_size += b->yy_buf_size / 8; @@ -1151,11 +1617,12 @@ static int yy_get_next_buffer (yyscan_t yyscanner) b->yy_ch_buf = (char *) /* Include room in for 2 EOB chars. */ - tri_v8_realloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner ); + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); } else /* Can't grow it, we don't own it. */ - b->yy_ch_buf = 0; + b->yy_ch_buf = NULL; if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( @@ -1183,7 +1650,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner) if ( number_to_move == YY_MORE_ADJ ) { ret_val = EOB_ACT_END_OF_FILE; - tri_v8_restart(yyin ,yyscanner); + yyrestart( yyin , yyscanner); } else @@ -1197,12 +1664,15 @@ static int yy_get_next_buffer (yyscan_t yyscanner) else ret_val = EOB_ACT_CONTINUE_SCAN; - if ((yy_size_t) (yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { /* Extend the array by 50%, plus the number we really need. */ - yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); - YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) tri_v8_realloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ,yyscanner ); + int new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); } yyg->yy_n_chars += number_to_move; @@ -1214,6 +1684,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner) return ret_val; } + /* yy_get_previous_state - get the state just before the EOB char was reached */ static yy_state_type yy_get_previous_state (yyscan_t yyscanner) @@ -1236,14 +1707,15 @@ static int yy_get_next_buffer (yyscan_t yyscanner) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 45 ) - yy_c = yy_meta[(unsigned int) yy_c]; + yy_c = yy_meta[yy_c]; } - yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; } return yy_current_state; } + /* yy_try_NUL_trans - try to make a transition on the NUL character * * synopsis @@ -1265,14 +1737,20 @@ static int yy_get_next_buffer (yyscan_t yyscanner) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 45 ) - yy_c = yy_meta[(unsigned int) yy_c]; + yy_c = yy_meta[yy_c]; } - yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; yy_is_jam = (yy_current_state == 44); + (void)yyg; return yy_is_jam ? 0 : yy_current_state; } + +#ifndef YY_NO_UNPUT + +#endif + #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput (yyscan_t yyscanner) @@ -1298,7 +1776,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner) else { /* need more input */ - yy_size_t offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + int offset = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr); ++yyg->yy_c_buf_p; switch ( yy_get_next_buffer( yyscanner ) ) @@ -1315,13 +1793,13 @@ static int yy_get_next_buffer (yyscan_t yyscanner) */ /* Reset buffer status. */ - tri_v8_restart(yyin ,yyscanner); + yyrestart( yyin , yyscanner); /*FALLTHROUGH*/ case EOB_ACT_END_OF_FILE: { - if ( tri_v8_wrap(yyscanner ) ) + if ( yywrap( yyscanner ) ) return 0; if ( ! yyg->yy_did_buffer_switch_on_eof ) @@ -1344,6 +1822,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner) *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ yyg->yy_hold_char = *++yyg->yy_c_buf_p; + return c; } #endif /* ifndef YY_NO_INPUT */ @@ -1353,34 +1832,35 @@ static int yy_get_next_buffer (yyscan_t yyscanner) * @param yyscanner The scanner object. * @note This function does not reset the start condition to @c INITIAL . */ - void tri_v8_restart (FILE * input_file , yyscan_t yyscanner) + void yyrestart (FILE * input_file , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! YY_CURRENT_BUFFER ){ - tri_v8_ensure_buffer_stack (yyscanner); + yyensure_buffer_stack (yyscanner); YY_CURRENT_BUFFER_LVALUE = - tri_v8__create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); } - tri_v8__init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner); - tri_v8__load_buffer_state(yyscanner ); + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); } + /** Switch to a different input buffer. * @param new_buffer The new input buffer. * @param yyscanner The scanner object. */ - void tri_v8__switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* TODO. We should be able to replace this entire function body * with - * tri_v8_pop_buffer_state(); - * tri_v8_push_buffer_state(new_buffer); + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); */ - tri_v8_ensure_buffer_stack (yyscanner); + yyensure_buffer_stack (yyscanner); if ( YY_CURRENT_BUFFER == new_buffer ) return; @@ -1393,17 +1873,18 @@ static int yy_get_next_buffer (yyscan_t yyscanner) } YY_CURRENT_BUFFER_LVALUE = new_buffer; - tri_v8__load_buffer_state(yyscanner ); + yy_load_buffer_state( yyscanner ); /* We don't actually know whether we did this switch during - * EOF (tri_v8_wrap()) processing, but the only time this flag - * is looked at is after tri_v8_wrap() is called, so it's safe + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe * to go ahead and always set it. */ yyg->yy_did_buffer_switch_on_eof = 1; } -static void tri_v8__load_buffer_state (yyscan_t yyscanner) + +static void yy_load_buffer_state (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; @@ -1418,35 +1899,36 @@ static void tri_v8__load_buffer_state (yyscan_t yyscanner) * @param yyscanner The scanner object. * @return the allocated buffer state. */ - YY_BUFFER_STATE tri_v8__create_buffer (FILE * file, int size , yyscan_t yyscanner) + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) { YY_BUFFER_STATE b; - b = (YY_BUFFER_STATE) tri_v8_alloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); if ( ! b ) - YY_FATAL_ERROR( "out of dynamic memory in tri_v8__create_buffer()" ); + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_buf_size = size; /* yy_ch_buf has to be 2 characters longer than the size given because * we need to put in 2 end-of-buffer characters. */ - b->yy_ch_buf = (char *) tri_v8_alloc(b->yy_buf_size + 2 ,yyscanner ); + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); if ( ! b->yy_ch_buf ) - YY_FATAL_ERROR( "out of dynamic memory in tri_v8__create_buffer()" ); + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_is_our_buffer = 1; - tri_v8__init_buffer(b,file ,yyscanner); + yy_init_buffer( b, file , yyscanner); return b; } + /** Destroy the buffer. - * @param b a buffer created with tri_v8__create_buffer() + * @param b a buffer created with yy_create_buffer() * @param yyscanner The scanner object. */ - void tri_v8__delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; @@ -1457,32 +1939,29 @@ static void tri_v8__load_buffer_state (yyscan_t yyscanner) YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; if ( b->yy_is_our_buffer ) - tri_v8_free((void *) b->yy_ch_buf ,yyscanner ); + yyfree( (void *) b->yy_ch_buf , yyscanner ); - tri_v8_free((void *) b ,yyscanner ); + yyfree( (void *) b , yyscanner ); } -#ifndef __cplusplus -extern int isatty (int ); -#endif /* __cplusplus */ - + /* Initializes or reinitializes a buffer. * This function is sometimes called more than once on the same buffer, - * such as during a tri_v8_restart() or at EOF. + * such as during a yyrestart() or at EOF. */ - static void tri_v8__init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) { int oerrno = errno; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; - tri_v8__flush_buffer(b ,yyscanner); + yy_flush_buffer( b , yyscanner); b->yy_input_file = file; b->yy_fill_buffer = 1; - /* If b is the current buffer, then tri_v8__init_buffer was _probably_ - * called from tri_v8_restart() or through yy_get_next_buffer. + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. * In that case, we don't want to reset the lineno or column. */ if (b != YY_CURRENT_BUFFER){ @@ -1490,8 +1969,11 @@ extern int isatty (int ); b->yy_bs_column = 0; } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + errno = oerrno; } @@ -1499,7 +1981,7 @@ extern int isatty (int ); * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. * @param yyscanner The scanner object. */ - void tri_v8__flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! b ) @@ -1520,7 +2002,7 @@ extern int isatty (int ); b->yy_buffer_status = YY_BUFFER_NEW; if ( b == YY_CURRENT_BUFFER ) - tri_v8__load_buffer_state(yyscanner ); + yy_load_buffer_state( yyscanner ); } /** Pushes the new state onto the stack. The new state becomes @@ -1529,15 +2011,15 @@ extern int isatty (int ); * @param new_buffer The new state. * @param yyscanner The scanner object. */ -void tri_v8_push_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (new_buffer == NULL) return; - tri_v8_ensure_buffer_stack(yyscanner); + yyensure_buffer_stack(yyscanner); - /* This block is copied from tri_v8__switch_to_buffer. */ + /* This block is copied from yy_switch_to_buffer. */ if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ @@ -1551,36 +2033,38 @@ void tri_v8_push_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) yyg->yy_buffer_stack_top++; YY_CURRENT_BUFFER_LVALUE = new_buffer; - /* copied from tri_v8__switch_to_buffer. */ - tri_v8__load_buffer_state(yyscanner ); + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); yyg->yy_did_buffer_switch_on_eof = 1; } + /** Removes and deletes the top of the stack, if present. * The next element becomes the new top. * @param yyscanner The scanner object. */ -void tri_v8_pop_buffer_state (yyscan_t yyscanner) +void yypop_buffer_state (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (!YY_CURRENT_BUFFER) return; - tri_v8__delete_buffer(YY_CURRENT_BUFFER ,yyscanner); + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); YY_CURRENT_BUFFER_LVALUE = NULL; if (yyg->yy_buffer_stack_top > 0) --yyg->yy_buffer_stack_top; if (YY_CURRENT_BUFFER) { - tri_v8__load_buffer_state(yyscanner ); + yy_load_buffer_state( yyscanner ); yyg->yy_did_buffer_switch_on_eof = 1; } } + /* Allocates the stack if it does not exist. * Guarantees space for at least one push. */ -static void tri_v8_ensure_buffer_stack (yyscan_t yyscanner) +static void yyensure_buffer_stack (yyscan_t yyscanner) { yy_size_t num_to_alloc; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; @@ -1591,15 +2075,16 @@ static void tri_v8_ensure_buffer_stack (yyscan_t yyscanner) * scanner will even need a stack. We use 2 instead of 1 to avoid an * immediate realloc on the next call. */ - num_to_alloc = 1; - yyg->yy_buffer_stack = (struct yy_buffer_state**)tri_v8_alloc + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc (num_to_alloc * sizeof(struct yy_buffer_state*) , yyscanner); if ( ! yyg->yy_buffer_stack ) - YY_FATAL_ERROR( "out of dynamic memory in tri_v8_ensure_buffer_stack()" ); - + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); - + yyg->yy_buffer_stack_max = num_to_alloc; yyg->yy_buffer_stack_top = 0; return; @@ -1608,15 +2093,15 @@ static void tri_v8_ensure_buffer_stack (yyscan_t yyscanner) if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ /* Increase the buffer to prepare for a possible push. */ - int grow_size = 8 /* arbitrary grow size */; + yy_size_t grow_size = 8 /* arbitrary grow size */; num_to_alloc = yyg->yy_buffer_stack_max + grow_size; - yyg->yy_buffer_stack = (struct yy_buffer_state**)tri_v8_realloc + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc (yyg->yy_buffer_stack, num_to_alloc * sizeof(struct yy_buffer_state*) , yyscanner); if ( ! yyg->yy_buffer_stack ) - YY_FATAL_ERROR( "out of dynamic memory in tri_v8_ensure_buffer_stack()" ); + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); /* zero only the new slots.*/ memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); @@ -1624,13 +2109,17 @@ static void tri_v8_ensure_buffer_stack (yyscan_t yyscanner) } } + + + + /** Setup the input buffer state to scan directly from a user-specified character buffer. * @param base the character buffer * @param size the size in bytes of the character buffer * @param yyscanner The scanner object. - * @return the newly allocated buffer state object. + * @return the newly allocated buffer state object. */ -YY_BUFFER_STATE tri_v8__scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) { YY_BUFFER_STATE b; @@ -1638,68 +2127,75 @@ YY_BUFFER_STATE tri_v8__scan_buffer (char * base, yy_size_t size , yyscan_t yy base[size-2] != YY_END_OF_BUFFER_CHAR || base[size-1] != YY_END_OF_BUFFER_CHAR ) /* They forgot to leave room for the EOB's. */ - return 0; + return NULL; - b = (YY_BUFFER_STATE) tri_v8_alloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); if ( ! b ) - YY_FATAL_ERROR( "out of dynamic memory in tri_v8__scan_buffer()" ); + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); - b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ b->yy_buf_pos = b->yy_ch_buf = base; b->yy_is_our_buffer = 0; - b->yy_input_file = 0; + b->yy_input_file = NULL; b->yy_n_chars = b->yy_buf_size; b->yy_is_interactive = 0; b->yy_at_bol = 1; b->yy_fill_buffer = 0; b->yy_buffer_status = YY_BUFFER_NEW; - tri_v8__switch_to_buffer(b ,yyscanner ); + yy_switch_to_buffer( b , yyscanner ); return b; } -/** Setup the input buffer state to scan a string. The next call to tri_v8_lex() will + + + +/** Setup the input buffer state to scan a string. The next call to yylex() will * scan from a @e copy of @a str. * @param yystr a NUL-terminated string to scan * @param yyscanner The scanner object. * @return the newly allocated buffer state object. * @note If you want to scan bytes that may contain NUL values, then use - * tri_v8__scan_bytes() instead. + * yy_scan_bytes() instead. */ -YY_BUFFER_STATE tri_v8__scan_string (yyconst char * yystr , yyscan_t yyscanner) +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) { - return tri_v8__scan_bytes(yystr,strlen(yystr) ,yyscanner); + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); } -/** Setup the input buffer state to scan the given bytes. The next call to tri_v8_lex() will + + + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will * scan from a @e copy of @a bytes. - * @param bytes the byte buffer to scan - * @param len the number of bytes in the buffer pointed to by @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. * @param yyscanner The scanner object. * @return the newly allocated buffer state object. */ -YY_BUFFER_STATE tri_v8__scan_bytes (yyconst char * yybytes, yy_size_t _yybytes_len , yyscan_t yyscanner) +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len , yyscan_t yyscanner) { YY_BUFFER_STATE b; char *buf; - yy_size_t n, i; + yy_size_t n; + int i; /* Get memory for full buffer, including space for trailing EOB's. */ - n = _yybytes_len + 2; - buf = (char *) tri_v8_alloc(n ,yyscanner ); + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); if ( ! buf ) - YY_FATAL_ERROR( "out of dynamic memory in tri_v8__scan_bytes()" ); + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); for ( i = 0; i < _yybytes_len; ++i ) buf[i] = yybytes[i]; buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; - b = tri_v8__scan_buffer(buf,n ,yyscanner); + b = yy_scan_buffer( buf, n , yyscanner); if ( ! b ) - YY_FATAL_ERROR( "bad buffer in tri_v8__scan_bytes()" ); + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); /* It's okay to grow etc. this buffer, and we should throw it * away when we're done. @@ -1709,13 +2205,25 @@ YY_BUFFER_STATE tri_v8__scan_bytes (yyconst char * yybytes, yy_size_t _yybytes return b; } + + + + + + + + + + #ifndef YY_EXIT_FAILURE #define YY_EXIT_FAILURE 2 #endif -static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner) +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) { - (void) fprintf( stderr, "%s\n", msg ); + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); exit( YY_EXIT_FAILURE ); } @@ -1736,23 +2244,29 @@ static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner) } \ while ( 0 ) + + /* Accessor methods (get/set functions) to struct members. */ + /** Get the user-defined data for this scanner. * @param yyscanner The scanner object. */ -YY_EXTRA_TYPE tri_v8_get_extra (yyscan_t yyscanner) +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyextra; } + + /** Get the current line number. * @param yyscanner The scanner object. */ -int tri_v8_get_lineno (yyscan_t yyscanner) +int yyget_lineno (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (! YY_CURRENT_BUFFER) return 0; @@ -1760,12 +2274,16 @@ int tri_v8_get_lineno (yyscan_t yyscanner) return yylineno; } + + + /** Get the current column number. * @param yyscanner The scanner object. */ -int tri_v8_get_column (yyscan_t yyscanner) +int yyget_column (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (! YY_CURRENT_BUFFER) return 0; @@ -1773,131 +2291,160 @@ int tri_v8_get_column (yyscan_t yyscanner) return yycolumn; } + + + /** Get the input stream. * @param yyscanner The scanner object. */ -FILE *tri_v8_get_in (yyscan_t yyscanner) +FILE *yyget_in (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyin; } + + /** Get the output stream. * @param yyscanner The scanner object. */ -FILE *tri_v8_get_out (yyscan_t yyscanner) +FILE *yyget_out (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyout; } + + /** Get the length of the current token. * @param yyscanner The scanner object. */ -yy_size_t tri_v8_get_leng (yyscan_t yyscanner) +int yyget_leng (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyleng; } + /** Get the current token. * @param yyscanner The scanner object. */ -char *tri_v8_get_text (yyscan_t yyscanner) +char *yyget_text (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yytext; } + + /** Set the user-defined data. This data is never touched by the scanner. * @param user_defined The data to be associated with this scanner. * @param yyscanner The scanner object. */ -void tri_v8_set_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyextra = user_defined ; } + + /** Set the current line number. - * @param line_number + * @param _line_number line number * @param yyscanner The scanner object. */ -void tri_v8_set_lineno (int line_number , yyscan_t yyscanner) +void yyset_lineno (int _line_number , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* lineno is only valid if an input buffer exists. */ if (! YY_CURRENT_BUFFER ) - yy_fatal_error( "tri_v8_set_lineno called with no buffer" , yyscanner); + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); - yylineno = line_number; + yylineno = _line_number; } + + + /** Set the current column. - * @param line_number + * @param _column_no column number * @param yyscanner The scanner object. */ -void tri_v8_set_column (int column_no , yyscan_t yyscanner) +void yyset_column (int _column_no , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* column is only valid if an input buffer exists. */ if (! YY_CURRENT_BUFFER ) - yy_fatal_error( "tri_v8_set_column called with no buffer" , yyscanner); + YY_FATAL_ERROR( "yyset_column called with no buffer" ); - yycolumn = column_no; + yycolumn = _column_no; } + + + + /** Set the input stream. This does not discard the current * input buffer. - * @param in_str A readable stream. + * @param _in_str A readable stream. * @param yyscanner The scanner object. - * @see tri_v8__switch_to_buffer + * @see yy_switch_to_buffer */ -void tri_v8_set_in (FILE * in_str , yyscan_t yyscanner) +void yyset_in (FILE * _in_str , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; - yyin = in_str ; + yyin = _in_str ; } -void tri_v8_set_out (FILE * out_str , yyscan_t yyscanner) + + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; - yyout = out_str ; + yyout = _out_str ; } -int tri_v8_get_debug (yyscan_t yyscanner) + + + +int yyget_debug (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yy_flex_debug; } -void tri_v8_set_debug (int bdebug , yyscan_t yyscanner) + + +void yyset_debug (int _bdebug , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; - yy_flex_debug = bdebug ; + yy_flex_debug = _bdebug ; } + /* Accessor methods for yylval and yylloc */ + + /* User-visible API */ -/* tri_v8_lex_init is special because it creates the scanner itself, so it is +/* yylex_init is special because it creates the scanner itself, so it is * the ONLY reentrant function that doesn't take the scanner as the last argument. * That's why we explicitly handle the declaration, instead of using our macros. */ - -int tri_v8_lex_init(yyscan_t* ptr_yy_globals) - +int yylex_init(yyscan_t* ptr_yy_globals) { if (ptr_yy_globals == NULL){ errno = EINVAL; return 1; } - *ptr_yy_globals = (yyscan_t) tri_v8_alloc ( sizeof( struct yyguts_t ), NULL ); + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); if (*ptr_yy_globals == NULL){ errno = ENOMEM; @@ -1910,120 +2457,141 @@ int tri_v8_lex_init(yyscan_t* ptr_yy_globals) return yy_init_globals ( *ptr_yy_globals ); } -/* tri_v8_lex_init_extra has the same functionality as tri_v8_lex_init, but follows the + +/* yylex_init_extra has the same functionality as yylex_init, but follows the * convention of taking the scanner as the last argument. Note however, that * this is a *pointer* to a scanner, as it will be allocated by this call (and * is the reason, too, why this function also must handle its own declaration). - * The user defined value in the first argument will be available to tri_v8_alloc in + * The user defined value in the first argument will be available to yyalloc in * the yyextra field. */ - -int tri_v8_lex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals ) - +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) { struct yyguts_t dummy_yyguts; - tri_v8_set_extra (yy_user_defined, &dummy_yyguts); + yyset_extra (yy_user_defined, &dummy_yyguts); if (ptr_yy_globals == NULL){ errno = EINVAL; return 1; } - - *ptr_yy_globals = (yyscan_t) tri_v8_alloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); - + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + if (*ptr_yy_globals == NULL){ errno = ENOMEM; return 1; } - + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); - - tri_v8_set_extra (yy_user_defined, *ptr_yy_globals); - + + yyset_extra (yy_user_defined, *ptr_yy_globals); + return yy_init_globals ( *ptr_yy_globals ); } + static int yy_init_globals (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* Initialization is the same as for the non-reentrant scanner. - * This function is called from tri_v8_lex_destroy(), so don't allocate here. + * This function is called from yylex_destroy(), so don't allocate here. */ - yyg->yy_buffer_stack = 0; + + yyg->yy_buffer_stack = NULL; yyg->yy_buffer_stack_top = 0; yyg->yy_buffer_stack_max = 0; - yyg->yy_c_buf_p = (char *) 0; + yyg->yy_c_buf_p = NULL; yyg->yy_init = 0; yyg->yy_start = 0; + yyg->yy_start_stack_ptr = 0; yyg->yy_start_stack_depth = 0; yyg->yy_start_stack = NULL; + + + + + /* Defined in main.c */ #ifdef YY_STDINIT yyin = stdin; yyout = stdout; #else - yyin = (FILE *) 0; - yyout = (FILE *) 0; + yyin = NULL; + yyout = NULL; #endif /* For future reference: Set errno on error, since we are called by - * tri_v8_lex_init() + * yylex_init() */ return 0; } -/* tri_v8_lex_destroy is for both reentrant and non-reentrant scanners. */ -int tri_v8_lex_destroy (yyscan_t yyscanner) + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* Pop the buffer stack, destroying each element. */ while(YY_CURRENT_BUFFER){ - tri_v8__delete_buffer(YY_CURRENT_BUFFER ,yyscanner ); + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); YY_CURRENT_BUFFER_LVALUE = NULL; - tri_v8_pop_buffer_state(yyscanner); + yypop_buffer_state(yyscanner); } /* Destroy the stack itself. */ - tri_v8_free(yyg->yy_buffer_stack ,yyscanner); + yyfree(yyg->yy_buffer_stack , yyscanner); yyg->yy_buffer_stack = NULL; + /* Destroy the start condition stack. */ - tri_v8_free(yyg->yy_start_stack ,yyscanner ); + yyfree( yyg->yy_start_stack , yyscanner ); yyg->yy_start_stack = NULL; + + + /* Reset the globals. This is important in a non-reentrant scanner so the next time - * tri_v8_lex() is called, initialization will occur. */ + * yylex() is called, initialization will occur. */ yy_init_globals( yyscanner); /* Destroy the main struct (reentrant only). */ - tri_v8_free ( yyscanner , yyscanner ); + yyfree ( yyscanner , yyscanner ); yyscanner = NULL; return 0; } + + /* * Internal utility routines. */ + + #ifndef yytext_ptr -static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner) +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) { + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + int i; for ( i = 0; i < n; ++i ) s1[i] = s2[i]; } #endif + + #ifdef YY_NEED_STRLEN -static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner) +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) { int n; for ( n = 0; s[n]; ++n ) @@ -2033,13 +2601,22 @@ static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner) } #endif -void *tri_v8_alloc (yy_size_t size , yyscan_t yyscanner) + + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) { - return (void *) malloc( size ); + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); } -void *tri_v8_realloc (void * ptr, yy_size_t size , yyscan_t yyscanner) + + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) { + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + /* The cast to (char *) in the following accommodates both * implementations that use char* generic pointers, and those * that use void* generic pointers. It works with the latter @@ -2047,16 +2624,28 @@ void *tri_v8_realloc (void * ptr, yy_size_t size , yyscan_t yyscanner) * any pointer type to void*, and deal with argument conversions * as though doing an assignment. */ - return (void *) realloc( (char *) ptr, size ); + return realloc(ptr, size); } -void tri_v8_free (void * ptr , yyscan_t yyscanner) + + +void yyfree (void * ptr , yyscan_t yyscanner) { - free( (char *) ptr ); /* see tri_v8_realloc() for (char *) cast */ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ } + #define YYTABLES_NAME "yytables" + + + + + + + // ----------------------------------------------------------------------------- // --SECTION-- forward declarations // ----------------------------------------------------------------------------- @@ -2079,7 +2668,7 @@ static v8::Handle ParseArray (v8::Isolate* isolate, v8::Handle array = v8::Array::New(isolate); uint32_t pos = 0; - int c = tri_v8_lex(scanner); + int c = yylex(scanner); while (c != END_OF_FILE) { if (c == CLOSE_BRACKET) { @@ -2092,7 +2681,7 @@ static v8::Handle ParseArray (v8::Isolate* isolate, return v8::Undefined(isolate); } - c = tri_v8_lex(scanner); + c = yylex(scanner); } v8::Handle sub = ParseValue(isolate, scanner, c); @@ -2104,7 +2693,7 @@ static v8::Handle ParseArray (v8::Isolate* isolate, array->Set(pos++, sub); - c = tri_v8_lex(scanner); + c = yylex(scanner); } yyextra._message = "expecting an array element, got end-of-file"; @@ -2123,7 +2712,7 @@ static v8::Handle ParseObject (v8::Isolate* isolate, v8::Handle object = v8::Object::New(isolate); bool comma = false; - int c = tri_v8_lex(scanner); + int c = yylex(scanner); while (c != END_OF_FILE) { if (c == CLOSE_BRACE) { @@ -2136,7 +2725,7 @@ static v8::Handle ParseObject (v8::Isolate* isolate, return v8::Undefined(isolate); } - c = tri_v8_lex(scanner); + c = yylex(scanner); } else { comma = true; @@ -2168,7 +2757,7 @@ static v8::Handle ParseObject (v8::Isolate* isolate, } // followed by a colon - c = tri_v8_lex(scanner); + c = yylex(scanner); if (c != COLON) { yyextra._message = "expecting colon"; @@ -2176,7 +2765,7 @@ static v8::Handle ParseObject (v8::Isolate* isolate, } // followed by an object - c = tri_v8_lex(scanner); + c = yylex(scanner); v8::Handle sub = ParseValue(isolate, scanner, c); if (sub->IsUndefined()) { @@ -2186,7 +2775,7 @@ static v8::Handle ParseObject (v8::Isolate* isolate, object->ForceSet(attributeName, sub); - c = tri_v8_lex(scanner); + c = yylex(scanner); } yyextra._message = "expecting an object attribute name or element, got end-of-file"; @@ -2334,16 +2923,16 @@ v8::Handle TRI_FromJsonString (v8::Isolate* isolate, size_t len, char** error) { yyscan_t scanner; - tri_v8_lex_init(&scanner); + yylex_init(&scanner); struct yyguts_t* yyg = (struct yyguts_t*) scanner; - YY_BUFFER_STATE buf = tri_v8__scan_bytes(text,len,scanner); + YY_BUFFER_STATE buf = yy_scan_bytes(text, len, scanner); - int c = tri_v8_lex(scanner); + int c = yylex(scanner); v8::Handle value = ParseValue(isolate, scanner, c); if (!value->IsUndefined()) { - c = tri_v8_lex(scanner); + c = yylex(scanner); if (c != END_OF_FILE) { value = v8::Undefined(isolate); @@ -2359,8 +2948,8 @@ v8::Handle TRI_FromJsonString (v8::Isolate* isolate, } } - tri_v8__delete_buffer(buf,scanner); - tri_v8_lex_destroy(scanner); + yy_delete_buffer(buf, scanner); + yylex_destroy(scanner); return value; }