/*jshint strict: false, unused: false */ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality /// /// @file /// /// DISCLAIMER /// /// Copyright 2010-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 Dr. Frank Celler, Lucas Dohmen /// @author Copyright 2011-2012, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// var arangodb = require("@arangodb"), is = require("@arangodb/is"), db = arangodb.db, ArangoCollection = arangodb.ArangoCollection, common = require("@arangodb/graph-common"), newGraph = require("@arangodb/general-graph"), Edge = common.Edge, Graph = common.Graph, Vertex = common.Vertex, GraphArray = common.GraphArray, Iterator = common.Iterator; //////////////////////////////////////////////////////////////////////////////// /// @brief find or create a collection by name //////////////////////////////////////////////////////////////////////////////// var findOrCreateCollectionByName = function (name) { var col = db._collection(name); if (col === null) { col = db._create(name); } else if (!(col instanceof ArangoCollection) || col.type() !== ArangoCollection.TYPE_DOCUMENT) { throw "<" + name + "> must be a document collection"; } if (col === null) { throw "collection '" + name + "' has vanished"; } return col; }; //////////////////////////////////////////////////////////////////////////////// /// @brief find or create an edge collection by name //////////////////////////////////////////////////////////////////////////////// var findOrCreateEdgeCollectionByName = function (name) { var col = db._collection(name); if (col === null) { col = db._createEdgeCollection(name); } else if (!(col instanceof ArangoCollection) || col.type() !== ArangoCollection.TYPE_EDGE) { throw "<" + name + "> must be an edge collection"; } if (col === null) { throw "collection '" + name + "' has vanished"; } return col; }; //////////////////////////////////////////////////////////////////////////////// /// @brief was docuBlock edgeSetProperty //////////////////////////////////////////////////////////////////////////////// Edge.prototype.setProperty = function (name, value) { var shallow = this._properties._shallowCopy; var id; // Could potentially change the weight of edges this._graph.emptyCachedPredecessors(); shallow.$label = this._properties.$label; shallow[name] = value; if (this._properties.hasOwnProperty('_from')) { shallow._from = this._properties._from; } if (this._properties.hasOwnProperty('_to')) { shallow._to = this._properties._to; } id = this._graph._edges.replace(this._properties, shallow); this._properties = this._graph._edges.document(id); return value; }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block vertexEdges /// /// `vertex.edges()` /// /// Returns a list of in- or outbound edges of the *vertex*. /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Vertex.prototype.edges = function () { var graph = this._graph; return graph._edges.edges(this._properties._id).map(function (result) { return graph.constructEdge(result); }); }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block vertexGetInEdges /// ///`vertex.getInEdges(label, ...)` /// /// Returns a list of inbound edges of the *vertex* with given label(s). /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Vertex.prototype.getInEdges = function () { var labels = Array.prototype.slice.call(arguments); var result = this.inbound(); if (labels.length > 0) { result = result.filter(function (edge) { return (labels.lastIndexOf(edge.getLabel()) > -1); }); } return result; }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block vertexGetOutEdges /// /// `vertex.getOutEdges(label, ...)` /// /// Returns a list of outbound edges of the *vertex* with given label(s). /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Vertex.prototype.getOutEdges = function () { var labels = Array.prototype.slice.call(arguments); var result = this.outbound(); if (labels.length > 0) { result = result.filter(function (edge) { return (labels.lastIndexOf(edge.getLabel()) > -1); }); } return result; }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block vertexGetEdges /// /// `vertex.getEdges(label, ...)` /// /// Returns a list of in- or outbound edges of the *vertex* with given /// label(s). /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Vertex.prototype.getEdges = function () { var labels = Array.prototype.slice.call(arguments); var result = this.edges(); if (labels.length > 0) { result = result.filter(function (edge) { return (labels.lastIndexOf(edge.getLabel()) > -1); }); } return result; }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block vertexInbound /// /// `vertex.inbound()` /// /// Returns a list of inbound edges of the *vertex*. /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Vertex.prototype.inbound = function () { var graph = this._graph; return graph._edges.inEdges(this._properties._id).map(function (result) { return graph.constructEdge(result); }); }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block vertexOutbound /// /// `vertex.outbound()` /// /// Returns a list of outbound edges of the *vertex*. /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Vertex.prototype.outbound = function () { var graph = this._graph; return graph._edges.outEdges(this._properties._id).map(function (result) { return graph.constructEdge(result); }); }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block vertexSetProperty /// /// `vertex.setProperty(name, value)` /// /// Changes or sets the property *name* a *vertex* to *value*. /// /// @end Docu BLock //////////////////////////////////////////////////////////////////////////////// Vertex.prototype.setProperty = function (name, value) { var shallow = this._properties._shallowCopy; var id; shallow[name] = value; // TODO use "update" if this becomes available id = this._graph._vertices.replace(this._properties, shallow); this._properties = this._graph._vertices.document(id); return value; }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block graphConstruct /// /// `Graph(name, vertices, edges)` /// /// Constructs a new graph object using the collection *vertices* for all /// vertices and the collection *edges* for all edges. Note that it is /// possible to construct two graphs with the same vertex set, but different /// edge sets. /// /// `Graph(name)` /// /// Returns a known graph. /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Graph.prototype.initialize = function (name, vertices, edges, waitForSync) { this._name = name; var gdb = db._collection("_graphs"); var graphProperties; var graphPropertiesId; if (gdb === null) { throw "_graphs collection does not exist."; } if (typeof name !== "string" || name === "") { throw " must be a string"; } // convert collection objects to collection names if (typeof vertices === 'object' && typeof vertices.name === 'function') { vertices = vertices.name(); } if (typeof edges === 'object' && typeof edges.name === 'function') { edges = edges.name(); } // find an existing graph by name if (vertices === undefined && edges === undefined) { try { graphProperties = gdb.document(name); } catch (e) { throw "no graph named '" + name + "' found"; } if (graphProperties === null) { throw "no graph named '" + name + "' found"; } //check if graph can be loaded by this deprecated module var newGraphError = "Graph can not be loaded, " + "because more than 1 vertex collection is defined. " + "Please use the new graph module"; var edgeDefinitions = db._graphs.document(name).edgeDefinitions; if (edgeDefinitions.length === 0) { throw newGraphError; } if (edgeDefinitions.length > 1) { throw newGraphError; } else if (edgeDefinitions.length === 1) { var from = edgeDefinitions[0].from; var to = edgeDefinitions[0].to; if (from.length !== 1 || to.length !== 1 || from[0] !== to[0]) { throw newGraphError; } } vertices = db._collection(edgeDefinitions[0].from[0]); if (vertices === null) { throw "vertex collection '" + edgeDefinitions[0].from[0] + "' has vanished"; } edges = db._collection(edgeDefinitions[0].collection); if (edges === null) { throw "edge collection '" + edgeDefinitions[0].collection + "' has vanished"; } } // sanity check for vertices else if (typeof vertices !== "string" || vertices === "") { throw " must be a string or null"; } // sanity check for edges else if (typeof edges !== "string" || edges === "") { throw " must be a string or null"; } // create a new graph or get an existing graph else { try { graphProperties = gdb.document(name); } catch (e1) { graphProperties = null; } // graph doesn't exist yet, create it if (graphProperties === null) { // check if know that graph graphProperties = gdb.firstExample( 'edgeDefintions', [{"collection": edges, "from" :[vertices], "to": [vertices]}] ); if (graphProperties === null) { // check if edge is used in a graph gdb.toArray().forEach( function(singleGraph) { var sGEDs = singleGraph.edgeDefinitions; sGEDs.forEach( function(sGED) { if (sGED.collection === edges) { graphProperties = ""; } } ); } ); if (graphProperties === null) { findOrCreateCollectionByName(vertices); findOrCreateEdgeCollectionByName(edges); var newEdgeDefinition = [{"collection": edges, "from" :[vertices], "to": [vertices]}]; graphPropertiesId = gdb.save( { 'edgeDefinitions' : newEdgeDefinition, '_key' : name }, waitForSync ); graphProperties = gdb.document(graphPropertiesId._key); } else { throw "edge collection already used"; } } else { throw "found graph but has different "; } } else { if (graphProperties.edgeDefinitions[0].from[0] !== vertices || graphProperties.edgeDefinitions[0].to[0] !== vertices || graphProperties.edgeDefinitions[0].collection !== edges) { throw "graph with that name already exists!"; } } vertices = db._collection(graphProperties.edgeDefinitions[0].from[0]); edges = db._collection(graphProperties.edgeDefinitions[0].collection); } this._properties = graphProperties; // and store the collections this._gdb = gdb; this._vertices = vertices; this._edges = edges; // and dictionary for vertices and edges this._verticesCache = {}; this._edgesCache = {}; // and store the caches this.predecessors = {}; this.distances = {}; }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block JSF_graph_getAll /// /// `graph.getAll()` /// /// Returns all available graphs. /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Graph.getAll = function getAllGraphs () { var gdb = db._collection("_graphs"), graphs = [ ]; gdb.toArray().forEach(function(doc) { try { var g = new Graph(doc._key); if (g._properties !== null) { graphs.push(g._properties); } } catch (err) { // if there's a problem, we just skip this graph } }); return graphs; }; //////////////////////////////////////////////////////////////////////////////// /// @brief static drop function //////////////////////////////////////////////////////////////////////////////// Graph.drop = function (name, waitForSync) { var gdb = db._collection("_graphs"); var exists = gdb.exists(name); try { var obj = new Graph(name); return obj.drop(waitForSync); } catch (err) { if (exists) { // if the graph exists but cannot be deleted because one of the underlying // collections is missing, delete from _graphs "manually" gdb.remove(name, true, waitForSync); } } }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block graphDrop /// /// `graph.drop(waitForSync)` /// /// Drops the graph, the vertices, and the edges. Handle with care. /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Graph.prototype.drop = function (waitForSync) { newGraph._drop(this._name, true); }; //////////////////////////////////////////////////////////////////////////////// /// @brief saves an edge to the graph //////////////////////////////////////////////////////////////////////////////// Graph.prototype._saveEdge = function(id, out_vertex_id, in_vertex_id, shallow, waitForSync) { this.emptyCachedPredecessors(); if (id !== undefined && id !== null) { shallow._key = String(id); } var ref = this._edges.save(out_vertex_id, in_vertex_id, shallow, waitForSync); return this.constructEdge(ref._id); }; //////////////////////////////////////////////////////////////////////////////// /// @brief saves a vertex to the graph //////////////////////////////////////////////////////////////////////////////// Graph.prototype._saveVertex = function (id, shallow, waitForSync) { var ref; if (is.existy(id)) { shallow._key = String(id); } ref = this._vertices.save(shallow, waitForSync); return this.constructVertex(ref._id); }; //////////////////////////////////////////////////////////////////////////////// /// @brief replaces a vertex to the graph //////////////////////////////////////////////////////////////////////////////// Graph.prototype._replaceVertex = function (vertex_id, data) { this._vertices.replace(vertex_id, data); }; //////////////////////////////////////////////////////////////////////////////// /// @brief replaces an edge in the graph //////////////////////////////////////////////////////////////////////////////// Graph.prototype._replaceEdge = function (edge_id, data) { this._edges.replace(edge_id, data); }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block graphGetVertex /// /// `graph.getVertex(id)` /// /// Returns the vertex identified by *id* or *null*. /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Graph.prototype.getVertex = function (id) { try { return this.constructVertex(id); } catch (e) { return null; } }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block GraphGetVertices /// /// `graph.getVertices()` /// /// Returns an iterator for all vertices of the graph. The iterator supports the /// methods *hasNext* and *next*. /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Graph.prototype.getVertices = function () { var all = this._vertices.all(), graph = this, wrapper = function(object) { return graph.constructVertex(object); }; return new Iterator(wrapper, all, "[edge iterator]"); }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block graphGetEdge /// /// `graph.getEdge(id)` /// /// Returns the edge identified by *id* or *null*. /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Graph.prototype.getEdge = function (id) { var ref, edge; try { ref = this._edges.document(id); } catch (e) { ref = null; } if (ref !== null) { edge = this.constructEdge(ref); } else { try { edge = this.constructEdge(id); } catch (e1) { edge = null; } } return edge; }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block graphGetEdges /// /// `graph.getEdges()` /// /// Returns an iterator for all edges of the graph. The iterator supports the /// methods *hasNext* and *next*. /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Graph.prototype.getEdges = function () { var all = this._edges.all(), graph = this, wrapper = function(object) { return graph.constructEdge(object); }; return new Iterator(wrapper, all, "[edge iterator]"); }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block graphRemoveVertex /// /// `graph.removeVertex(vertex, waitForSync)` /// /// Deletes the *vertex* and all its edges. /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Graph.prototype.removeVertex = function (vertex, waitForSync) { var result, graph = this; this.emptyCachedPredecessors(); if (vertex._properties) { result = this._vertices.remove(vertex._properties, true, waitForSync); if (!result) { throw "cannot delete vertex"; } vertex.edges().forEach(function (edge) { graph.removeEdge(edge, waitForSync); }); vertex._properties = undefined; } }; //////////////////////////////////////////////////////////////////////////////// /// @start Docu Block graphRemoveEdge /// /// `graph.removeEdge(vertex, waitForSync)` /// /// Deletes the *edge*. Note that the in and out vertices are left untouched. /// /// @end Docu Block //////////////////////////////////////////////////////////////////////////////// Graph.prototype.removeEdge = function (edge, waitForSync) { var result; this.emptyCachedPredecessors(); if (edge._properties) { result = this._edges.remove(edge._properties, true, waitForSync); if (! result) { throw "cannot delete edge"; } this._edgesCache[edge._properties._id] = undefined; edge._properties = undefined; } }; exports.Edge = Edge; exports.Graph = Graph; exports.Vertex = Vertex; exports.GraphArray = GraphArray; require("@arangodb/graph/algorithms-common");