1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
11 # Provides an interface for services on a Neo4j graphs.
12 module neo4j
::graph
::graph
17 # A Neo4j graph with a local identification scheme for its nodes.
19 # An identification scheme can be defined by subclassing `NeoNodeCollection`.
21 # `GraphStore` can be subclassed to add ways to save or load a graph. The
22 # storing mechanisms may use `nodes.id_of` to identify the nodes in the graph
23 # while encoding the relationships.
25 # All the nodes in the graph.
26 var nodes
: NeoNodeCollection
28 # All the relationships in the graph.
29 var edges
: SimpleCollection[NeoEdge] = new Array[NeoEdge]
31 # Add a new node to the graph and return it.
33 # Set the local ID of the node before returning it.
35 # SEE: `NeoNodeCollection.add`
36 # SEE: `NeoNodeCollection.create_node`
37 # SEE: `NeoNodeCollection.register`
38 fun create_node
: NeoNode do return nodes
.create_node
41 # All the nodes in a `NeoGraph`.
43 # An identification scheme can be defined throught the `register` and `add`
44 # methods. The `id_property` attribute defines where the local ID (that is the
45 # ID managed by the collection) is stored in each node.
46 abstract class NeoNodeCollection
47 super SimpleCollection[NeoNode]
49 # The type of the local IDs.
50 type ID_TYPE: Jsonable
52 # The property of the nodes that hold the local ID.
53 var id_property
: String
55 # Retrieve the node that has the specified local id.
57 # Note: The default implementation uses `get_or_null`.
58 fun [](id
: ID_TYPE): NeoNode do
59 var n
= get_or_null
(id
)
64 # Retrieve the node that has the specified local id, or return `null`.
66 # Note: The default implementation uses `iterator`.
67 fun get_or_null
(id
: ID_TYPE): nullable NeoNode do
69 if id_of
(n
) == id
then return n
74 # There is a node that has the specified local id?
76 # Note: The default implementation uses `get_or_null`.
77 fun has_id
(id
: ID_TYPE): Bool do return get_or_null
(id
) isa NeoNode
79 # Return the local ID of the node.
80 fun id_of
(node
: NeoNode): ID_TYPE do return node
[id_property
].as(ID_TYPE)
82 # Set the local ID of the specified node.
84 # Just update the property at `property_id`. Do not check anything.
85 protected fun id_of
=(node
: NeoNode, id
: ID_TYPE) do
86 node
[id_property
] = id
89 # Enlarge the collection to have at least the specified capacity.
91 # The capacity is specified in number of nodes. Used to minimize the
92 # number of times the collection need to be resized when adding nodes
95 # Do nothing by default.
96 fun enlarge
(cap
: Int) do end
98 # Add the specified node to the graph and set its local ID.
102 fun register
(node
: NeoNode) is abstract
104 # Add the specified node to the graph assuming that its local ID is already set.
108 redef fun add
(node
: NeoNode) is abstract
110 # Add a new node to the graph and return it.
112 # Set the local ID of the node before returning it.
116 fun create_node
: NeoNode do
117 var node
= new NeoNode
122 # Remove the node with the specified local ID.
123 fun remove_at
(id
: ID_TYPE) is abstract
125 # Remove the specified node.
127 # The local ID is used instead of `==` to seek the node.
128 fun remove_node
(node
: NeoNode) do
129 remove_at
(id_of
(node
))
133 for node
in self do remove_node
(node
)
136 redef fun remove
(node
: NeoNode) do
145 redef fun remove_all
(node
: NeoNode) do
147 if node
== n
then remove_node
(n
)
151 # Optimize the collection, possibly by rewritting it.
153 # The local ID of the elements may be changed by this method.
157 # A mean to save and load a Neo4j graph.
158 abstract class GraphStore
161 # The graph to save or load.
164 # Can we save the graph without conflict?
165 fun isolated_save
: Bool is abstract
167 # Load the graph (or a part of it).
169 # Do not reset the graph.
173 fun save
do save_part
(graph
.nodes
, graph
.edges
)
175 # Save the specified part of the graph.
177 # Assume that for each relationship specified, both ends are already saved
178 # or are specified in the same call to this method.
179 fun save_part
(nodes
: Collection[NeoNode],
180 edges
: Collection[NeoEdge]) is abstract
183 # Save or load a graph using an actual Neo4j database.
184 class Neo4jGraphStore
187 # The maximum number of entities saved in one request.
189 # Also defines the granulity of the reported progression.
191 # TODO Also honor this limit in `load`.
192 var batch_max_size
= 512 is writable
194 # The Neo4j client to use.
195 var client
: Neo4jClient
197 # The label to use to retrieve the nodes.
198 var node_label
: String
200 private var done_part
= 0
201 private var total
= 0
203 # Is the database already contains at least one node with the specified label?
204 fun has_node_label
(name
: String): Bool do
205 var query
= new CypherQuery.from_string
(
206 "match n where \{name\} in labels(n) return count(n)")
207 query
.params
["name"] = name
208 var data
= client
.cypher
(query
).as(JsonObject)["data"]
209 var result
= data
.as(JsonArray).first
.as(JsonArray).first
.as(Int)
213 redef fun isolated_save
do return not has_node_label
(node_label
)
216 assert batch_max_size
> 0
218 var db_nodes
= client
.nodes_with_label
(node_label
)
219 var nodes
= graph
.nodes
220 var edges
= graph
.edges
223 total
= nodes
.length
* 2
224 done_part
= nodes
.length
225 fire_progressed
(done_part
, total
)
226 for node
in db_nodes
do
228 edges
.add_all
(node
.out_edges
)
230 if i
>= batch_max_size
then
231 done_part
+= batch_max_size
232 fire_progressed
(done_part
, total
)
238 redef fun save_part
(nodes
, edges
) do
239 assert batch_max_size
> 0
241 total
= nodes
.length
+ edges
.length
249 # Save the specified entities.
250 private fun save_entities
(neo_entities
: Collection[NeoEntity]) do
251 var batch
= new NeoBatch(client
)
254 for nentity
in neo_entities
do
255 batch
.save_entity
(nentity
)
257 if batch_length
>= batch_max_size
then
259 done_part
+= batch_max_size
260 fire_progressed
(done_part
, total
)
261 batch
= new NeoBatch(client
)
266 done_part
+= batch_length
269 # Execute `batch` and check for errors.
271 # Abort if `batch.execute` returns errors.
272 private fun do_batch
(batch
: NeoBatch) do
273 var errors
= batch
.execute
274 assert errors
.is_empty
else
275 for e
in errors
do sys
.stderr
.write
("{e}\n")