pacer-neo4j 2.0.1-java → 2.1.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/pacer-neo4j.rb CHANGED
@@ -8,3 +8,77 @@ require 'pacer-neo4j/version'
8
8
  require Pacer::Neo4j::JAR
9
9
 
10
10
  require 'pacer-neo4j/graph'
11
+ require 'pacer-neo4j/algo/path_pipe'
12
+ require 'pacer-neo4j/algo/block_cost_evaluator'
13
+ require 'pacer-neo4j/algo/block_estimate_evaluator'
14
+ require 'pacer-neo4j/algo/block_path_expander'
15
+ require 'pacer-neo4j/algo/path_wrapper'
16
+ require 'pacer-neo4j/algo/traversal_branch_wrapper'
17
+ require 'pacer-neo4j/algo'
18
+ require 'pacer-neo4j/raw_vertex_wrapping_pipe'
19
+ require 'pacer-neo4j/lucene_filter'
20
+
21
+ Pacer::FunctionResolver.clear_cache
22
+
23
+ module Pacer
24
+ # Add 'static methods' to the Pacer namespace.
25
+ class << self
26
+ # Return a graph for the given path. Will create a graph if none exists at
27
+ # that location. (The graph is only created if data is actually added to it).
28
+ #
29
+ # If the graph is opened from a path, it will be registered to be closed by
30
+ # Ruby's at_exit callback, but if an already open graph is given, it will
31
+ # not.
32
+ #
33
+ # Please note that Pacer turns on Neo4j's checkElementsInTransaction
34
+ # feature by default. For some sort of performance improvement at
35
+ # the expense of an odd consistency model within transactions that
36
+ # require considerable more complexity in client code, you can use
37
+ # `graph.setCheckElementsInTransaction(false)` to disable the
38
+ # feature.
39
+ def neo4j(path_or_graph, args = nil)
40
+ bp_neo_class = com.tinkerpop.blueprints.impls.neo4j.Neo4jGraph
41
+ if path_or_graph.is_a? String
42
+ path = File.expand_path(path_or_graph)
43
+ open = proc do
44
+ graph = Pacer.open_graphs[path]
45
+ unless graph
46
+ if args
47
+ graph = bp_neo_class.new(path, args.to_hash_map)
48
+ else
49
+ graph = bp_neo_class.new(path)
50
+ end
51
+ Pacer.open_graphs[path] = graph
52
+ graph.setCheckElementsInTransaction true
53
+ end
54
+ graph
55
+ end
56
+ shutdown = proc do |g|
57
+ g.blueprints_graph.shutdown
58
+ Pacer.open_graphs.delete path
59
+ end
60
+ Neo4j::Graph.new(Pacer::YamlEncoder, open, shutdown)
61
+ else
62
+ # Don't register the new graph so that it won't be automatically closed.
63
+ Neo4j::Graph.new Pacer::YamlEncoder, proc { bp_neo_class.new(path_or_graph) }
64
+ end
65
+ end
66
+
67
+ def neo_batch(path)
68
+ bp_neo_class = com.tinkerpop.blueprints.impls.neo4jbatch.Neo4jBatchGraph
69
+ path = File.expand_path(path)
70
+ open = proc do
71
+ graph = bp_neo_class.new(path)
72
+ Pacer.open_graphs[path] = :open_batch_graph
73
+ graph
74
+ end
75
+ shutdown = proc do |g|
76
+ g.blueprints_graph.shutdown
77
+ Pacer.open_graphs.delete path
78
+ end
79
+ g = PacerGraph.new(Pacer::YamlEncoder, open, shutdown)
80
+ g.disable_transactions = true
81
+ g
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,406 @@
1
+ module Pacer
2
+ module Core::Graph
3
+ module VerticesRoute
4
+ def path_to(to_v, opts = {})
5
+ route = self
6
+ route = make_pairs(to_v) if to_v.is_a? Enumerable
7
+ route.chain_route({transform: :path_finder, element_type: :path, max_hits: 1, target: to_v}.merge opts)
8
+ end
9
+
10
+ def paths_to(to_v, opts = {})
11
+ route = self
12
+ route = make_pairs(to_v) if to_v.is_a? Enumerable
13
+ route.chain_route({transform: :path_finder, element_type: :path, target: to_v}.merge opts)
14
+ end
15
+ end
16
+
17
+ module PathRoute
18
+ def expand(opts = {})
19
+ chain_route({transform: :path_finder, element_type: :path}.merge opts)
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ module Transform
26
+ module PathFinder
27
+ import org.neo4j.graphalgo.CommonEvaluators
28
+ import org.neo4j.graphalgo.GraphAlgoFactory
29
+ import org.neo4j.graphdb.Direction
30
+ import org.neo4j.kernel.Traversal
31
+ import org.neo4j.graphdb.DynamicRelationshipType
32
+ include Pacer::Neo4j::Algo
33
+
34
+ def after_initialize
35
+ fail Pacer::ClientError, 'graph must be neo4j' unless graph.vendor == 'neo4j'
36
+ end
37
+
38
+ def help(opt = nil)
39
+ case opt
40
+ when nil
41
+ puts <<HELP
42
+ Finds paths between pairs of vertices. The algorithm used depends on the
43
+ options specified. All supported path finding algorithms including in Neo4j 1.8
44
+ are included, and all of their documented usages are possible.
45
+
46
+ USAGE:
47
+
48
+ vertices.path_to(targets, options = {})
49
+ Find the first path from each vertex to each target vertex
50
+
51
+ vertices.paths_to(targets, options = {})
52
+ Find multiple paths from each vertex to each target vertex
53
+
54
+ paths.expand(options = {})
55
+ Find multiple paths from the first vertex in each path to the last vertex
56
+ in each path.
57
+
58
+ All options are optional!
59
+
60
+ These methods only work on Neo4j graphs.
61
+
62
+ More details:
63
+
64
+ help :options for simple path algorithms and other options
65
+ find_all, cyclical, length, max_depth, max_hits
66
+
67
+ help :cost for Dijkstra and aStar
68
+ cost, cost_property, cost_default
69
+
70
+ help :estimate for aStar
71
+ estimate, estimate_default, lat_property, long_property
72
+
73
+ help :expansion customize how any path is expanded
74
+ in_labels, out_labels, both_labels, expander, forward, reverse
75
+
76
+ HELP
77
+ when :path
78
+ puts <<HELP
79
+ Details for path: expander: proc { |path, state| edges }
80
+
81
+ #end_v Returns the end vertex of this path.
82
+ #start_v Returns the start vertex of this path.
83
+ #length Returns the length of this path.
84
+ #path Iterates through both the vertices and edges of this path in
85
+ order.
86
+ #end_e Returns the last edge in this path.
87
+ #v Returns all the vertices in this path starting from the start
88
+ vertex going forward towards the end vertex.
89
+ #e Returns all the edges in between the vertices which this path
90
+ consists of.
91
+ #reverse_v Like #v but reversed.
92
+ #reverse_e Like #e but reversed.
93
+
94
+ The following methods all proxy to the vertex returned by #end_v: and behave
95
+ exactly like standard Pacer Vertex methods.
96
+
97
+ The iterators can be combined with the + operator.
98
+
99
+ Fast edge iterators:
100
+ #out_edges(*args)
101
+ #in_edges(*args)
102
+ #both_edges(*args)
103
+
104
+ Fast vertex iterators:
105
+ #out_vertices(*args)
106
+ #in_vertices(*args)
107
+ #both_vertices(*args)
108
+
109
+ Edge routes:
110
+ #out_e(*args)
111
+ #in_e(*args)
112
+ #both_e(*args)
113
+
114
+ Vertex routes:
115
+ #out(*args)
116
+ #in(*args)
117
+ #both(*args)
118
+
119
+ HELP
120
+ when :expansion
121
+ puts <<HELP
122
+ Path expansion options:
123
+
124
+ By default, all edges will be followed. By specifying expansion rules you can
125
+ limit which paths are attempted. All algorithms use the same expanders so
126
+ these options do not effect the algorithm selection.
127
+
128
+ in_labels: label | [labels] only follow : in edges : with the given label(s)
129
+ out_labels: : out edges :
130
+ both_labels: : edges :
131
+ These options can be combined.
132
+
133
+ Expanders search forward from the start vertex and backwards from the target
134
+ vertex. Either expander
135
+
136
+ expander: Proc | PathExpander Custom rule for forward search
137
+ If no reverse is specified, will be used for reverse too.
138
+ forward: synonym for the expander option
139
+ reverse: Proc | PathExpander Custom rule for the reverse search
140
+
141
+ proc { |path, state| edges }:
142
+ path is a Pacer::Neo4j::Algo::PathWrapper - help(:path) for details
143
+ The proc must simply return an Enumerable of edges that the
144
+
145
+ HELP
146
+ when :options
147
+ puts <<HELP
148
+ Simple options:
149
+
150
+ find_all: Boolean Find all non-cyclical paths.
151
+ Algorithm: allSimplePaths
152
+
153
+ cyclical: Boolean Find all paths including cyclical ones.
154
+ Algorithm: allPaths
155
+
156
+ length: Number Number of edges that the path contains.
157
+ Algorithm: pathsWithLength
158
+ Returns only paths of the specified length.
159
+
160
+ max_depth: Number Number of edges to search in a potential path.
161
+ Default: 5
162
+ Limits how many edges will be traversed searching for a path. Higher
163
+ numbers can take exponentially longer, I think. Does not apply to aStar,
164
+ Dijkstra, or pathsWithLength algorithms.
165
+
166
+ Required for find_all, cyclical, and shortest path algorithms.
167
+
168
+ max_hits: Number Maximum number of paths to find for each pair of vertices.
169
+ #path_to defaults this to 1. All algorithms use this but only
170
+ some support it natively in Neo4j's implementations.
171
+
172
+
173
+ HELP
174
+ when :cost
175
+ puts <<HELP
176
+ Cost options:
177
+
178
+ Specifying these chooses the Dijkstra algorithm unless an estimate is also
179
+ specified.
180
+
181
+ cost: Proc | CostEvaluator Calculate the cost for this edge.
182
+ Must return a Numeric unless cost_default is set.
183
+ proc { |edge, direction| Float }:
184
+ direction is either :in or :out.
185
+
186
+ cost_property: String get the cost from the given edge property
187
+ cost_default: Float default if the property isn't there
188
+
189
+ HELP
190
+ when :estimate
191
+ puts <<HELP
192
+ Estimate options
193
+ Specifying these together with cost chooses the a* / aStar algorithm.
194
+
195
+ estimate:
196
+ Must return a Numeric unless estimate_default is set.
197
+ proc { |vertex, goal_vertex| Float }
198
+
199
+ estimate_default: Float only works with the proc estimate
200
+
201
+ lat_property: String latitude property name
202
+ long_property: String longitude property name
203
+ Use latitude and longitude if all estimated vertices have the necessary
204
+ properties.
205
+
206
+ HELP
207
+ else
208
+ super
209
+ end
210
+ description
211
+ end
212
+
213
+ def method
214
+ if has_cost?
215
+ if has_estimate?
216
+ :aStar
217
+ else
218
+ :dijkstra
219
+ end
220
+ elsif cyclical and max_depth
221
+ :all
222
+ elsif find_all and max_depth
223
+ :all_simple
224
+ elsif length
225
+ :with_length
226
+ elsif max_depth
227
+ if max_hits
228
+ :shortest_with_max_hits
229
+ else
230
+ :shortest
231
+ end
232
+ end
233
+ end
234
+
235
+ attr_accessor :target
236
+
237
+ # specify one or many edge labels that the path may take in the given direction
238
+ attr_accessor :in_labels, :out_labels, :both_labels
239
+
240
+ # note that expander procs *must* return edge(s) that are connected to the end_v of the given path
241
+ #
242
+ # expander yields: { |path, state| path.is_a? Pacer::Neo4j::Algo::PathWrapper }
243
+ attr_accessor :expander, :forward, :reverse
244
+
245
+ # use dijkstra unless the below estimate properties are set
246
+ #
247
+ # note that cost proc must return a Numeric unless cost_default is set
248
+ #
249
+ # cost yields: { |edge, direction| [:in, :out, :both].include? direction }
250
+ attr_accessor :cost, :cost_property, :cost_default
251
+
252
+ def set_cost(property = nil, default = nil, &block)
253
+ self.cost_property = property
254
+ self.cost_default = default
255
+ self.cost = block
256
+ self
257
+ end
258
+
259
+ # use the aStar algorithm
260
+ #
261
+ # note that estimate proc must return a Numeric unless estimate_default is set
262
+ #
263
+ # estimate yields: { |vertex, goal_vertex| }
264
+ attr_accessor :estimate, :lat_property, :long_property, :estimate_default
265
+
266
+ def set_estimate(lat = nil, long = nil, &block)
267
+ self.lat_property = lat
268
+ self.long_property = long
269
+ self.estimate = block
270
+ self
271
+ end
272
+
273
+ # use pathsWithLength
274
+ #
275
+ # Return only paths of the given length
276
+ attr_accessor :length
277
+
278
+ # default to shortest_path unless find_all is set
279
+ attr_writer :max_depth
280
+ def max_depth
281
+ @max_depth || 5
282
+ end
283
+
284
+ # use shortestPath
285
+ attr_accessor :max_hits
286
+
287
+ # Possible values:
288
+ attr_accessor :find_all
289
+ attr_accessor :cyclical
290
+
291
+ protected
292
+
293
+ def attach_pipe(end_pipe)
294
+ if back.element_type == :path
295
+ p = PathFromPathPipe.new build_algo, graph, max_hits
296
+ else
297
+ p = PathPipe.new build_algo, graph, target, max_hits
298
+ end
299
+ p.setStarts end_pipe
300
+ attach_length_filter(p) if length and method != :with_length
301
+ p
302
+ end
303
+
304
+ def attach_length_filter(end_pipe)
305
+ pipe = Pacer::Pipes::BlockFilterPipe.new(self, proc { |p| p.length == length }, false)
306
+ pipe.set_starts end_pipe if end_pipe
307
+ pipe
308
+ end
309
+
310
+ def inspect_string
311
+ if back.element_type == :path
312
+ "expand[#{method}](max_depth: #{ max_depth })"
313
+ else
314
+ "paths_to[#{method}](#{ target.inspect }, max_depth: #{ max_depth })"
315
+ end
316
+ end
317
+
318
+ private
319
+
320
+ def has_cost?
321
+ cost or cost_property or cost_default
322
+ end
323
+
324
+ def has_estimate?
325
+ estimate or (lat_property and long_property) or estimate_default
326
+ end
327
+
328
+ def build_algo
329
+ case method
330
+ when :aStar
331
+ GraphAlgoFactory.aStar build_expander, build_cost, build_estimate
332
+ when :dijkstra
333
+ GraphAlgoFactory.dijkstra build_expander, build_cost
334
+ when :with_length
335
+ GraphAlgoFactory.pathsWithLength build_expander, length
336
+ when :all
337
+ GraphAlgoFactory.allPaths build_expander, max_depth
338
+ when :all_simple
339
+ GraphAlgoFactory.allSimplePaths build_expander, max_depth
340
+ when :shortest_with_max_hits
341
+ GraphAlgoFactory.shortestPath build_expander, max_depth, max_hits
342
+ when :shortest
343
+ GraphAlgoFactory.shortestPath build_expander, max_depth
344
+ when nil
345
+ fail Pacer::ClientError, "Could not choose a path algorithm"
346
+ else
347
+ fail Pacer::LogicError, "Unable to build algo for #{ method }"
348
+ end
349
+ end
350
+
351
+ def build_expander
352
+ if forward.is_a? Proc and reverse.is_a? Proc
353
+ BlockPathExpander.new forward, reverse, graph, max_depth
354
+ elsif expander.is_a? Proc
355
+ BlockPathExpander.new expander, expander, graph, max_depth
356
+ elsif expander
357
+ expander
358
+ else
359
+ e = Traversal.emptyExpander
360
+ [*out_labels].each do |label|
361
+ e.add DynamicRelationshipType.withName(label.to_s), Direction::OUTGOING
362
+ end
363
+ [*in_labels].each do |label|
364
+ e.add DynamicRelationshipType.withName(label.to_s), Direction::INCOMING
365
+ end
366
+ [*both_labels].each do |label|
367
+ e.add DynamicRelationshipType.withName(label.to_s), Direction::BOTH
368
+ end
369
+ e
370
+ end
371
+ end
372
+
373
+ def build_cost
374
+ if cost.is_a? Proc
375
+ BlockCostEvaluator.new cost, graph, cost_default
376
+ elsif cost
377
+ cost
378
+ elsif cost_property
379
+ if cost_default
380
+ CommonEvaluators.doubleCostEvaluator cost_property.to_s, cost_default.to_f
381
+ else
382
+ CommonEvaluators.doubleCostEvaluator cost_property.to_s
383
+ end
384
+ elsif cost_default
385
+ CommonEvaluators.doubleCostEvaluator ' not a property ', cost_default.to_f
386
+ else
387
+ fail Pacer::LogicError, "could not build cost"
388
+ end
389
+ end
390
+
391
+ def build_estimate
392
+ if estimate.is_a? Proc
393
+ BlockEstimateEvaluator.new estimate, graph, estimate_default
394
+ elsif estimate
395
+ estimate
396
+ elsif lat_property and long_property
397
+ CommonEvaluators.geoEstimateEvaluator lat_property.to_s, long_property.to_s
398
+ elsif estimate_default
399
+ BlockEstimateEvaluator.new proc { estimate_default }, graph, estimate_default
400
+ else
401
+ fail Pacer::LogicError, "could not build estimate"
402
+ end
403
+ end
404
+ end
405
+ end
406
+ end
@@ -0,0 +1,38 @@
1
+ module Pacer
2
+ module Neo4j
3
+ module Algo
4
+ class BlockCostEvaluator
5
+ import org.neo4j.graphalgo.CostEvaluator
6
+ import org.neo4j.graphdb.Direction
7
+ import com.tinkerpop.blueprints.impls.neo4j.Neo4jEdge
8
+ include CostEvaluator
9
+
10
+ DIRS = {
11
+ Direction::INCOMING => :in,
12
+ Direction::OUTGOING => :out,
13
+ Direction::BOTH => :both
14
+ }
15
+
16
+ attr_reader :block, :graph, :default
17
+
18
+ def initialize(block, graph, default)
19
+ @block = block
20
+ @graph = graph
21
+ @default = default.to_f
22
+ end
23
+
24
+ def getCost(rel, dir)
25
+ e = Pacer::Wrappers::EdgeWrapper.new graph, Neo4jEdge.new(rel, graph.blueprints_graph)
26
+ result = block.call e, DIRS[dir]
27
+ if result.is_a? Numeric
28
+ result.to_f
29
+ elsif default
30
+ default
31
+ else
32
+ fail Pacer::ClientError, "No cost returned and no default specified: #{ result.inspect }"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ module Pacer
2
+ module Neo4j
3
+ module Algo
4
+ class BlockEstimateEvaluator
5
+ import org.neo4j.graphalgo.EstimateEvaluator
6
+ import com.tinkerpop.blueprints.impls.neo4j.Neo4jVertex
7
+ include EstimateEvaluator
8
+
9
+ attr_reader :block, :graph, :default
10
+
11
+ def initialize(block, graph, default)
12
+ @block = block
13
+ @graph = graph
14
+ @default = default.to_f
15
+ end
16
+
17
+ def getCost(node, goal)
18
+ node = Pacer::Wrappers::VertexWrapper.new graph, Neo4jVertex.new(node, graph.blueprints_graph)
19
+ goal = Pacer::Wrappers::VertexWrapper.new graph, Neo4jVertex.new(goal, graph.blueprints_graph)
20
+ result = block.call node, goal
21
+ if result.is_a? Numeric
22
+ result.to_f
23
+ elsif default
24
+ default
25
+ else
26
+ fail Pacer::ClientError, "No estimate returned and no default specified: #{ result.inspect }"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,61 @@
1
+ module Pacer
2
+ module Neo4j
3
+ module Algo
4
+ class BlockPathExpander
5
+ import org.neo4j.graphdb.PathExpander
6
+ import com.tinkerpop.pipes.Pipe
7
+
8
+ include PathExpander
9
+ attr_reader :block, :rev, :graph, :max_depth
10
+
11
+ def initialize(block, rev, graph, max_depth)
12
+ @block = block
13
+ @rev = rev
14
+ @graph = graph
15
+ @max_depth = max_depth
16
+ end
17
+
18
+ def expand(path, state)
19
+ path = PathWrapper.new(path, graph)
20
+ if max_depth and path.length >= max_depth
21
+ result = []
22
+ else
23
+ result = block.call path, state
24
+ end
25
+ pipe = Pacer::Pipes::NakedPipe.new
26
+ pipe.set_starts result_to_enumerable(result)
27
+ pipe
28
+ end
29
+
30
+ def reverse
31
+ BlockPathExpander.new rev, block, graph, max_depth
32
+ end
33
+
34
+ def result_to_enumerable(result)
35
+ case result
36
+ when PathWrapper
37
+ fail "Don't just return the arguments in your expander, return edges!"
38
+ when Pacer::Route
39
+ if result.element_type == :edge
40
+ result.pipe.starts
41
+ else
42
+ fail "Expander must return edges"
43
+ end
44
+ when Pacer::Wrappers::EdgeWrapper
45
+ Pacer::Pipes::EnumerablePipe.new [result]
46
+ when Pacer::Pipes::WrappingPipe
47
+ result.starts
48
+ when Pipe
49
+ result
50
+ when Enumerable
51
+ Pacer::Pipes::EnumerablePipe.new result
52
+ when nil
53
+ Pacer::Pipes::EnumerablePipe.new []
54
+ else
55
+ fail "Can't figure out what to do with #{ result.class }"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,79 @@
1
+ module Pacer
2
+ module Neo4j
3
+ module Algo
4
+ class PathPipe < Pacer::Pipes::RubyPipe
5
+ import org.neo4j.graphdb::Node
6
+ import org.neo4j.graphdb::Relationship
7
+ import com.tinkerpop.blueprints.impls.neo4j.Neo4jVertex
8
+ import com.tinkerpop.blueprints.impls.neo4j.Neo4jEdge
9
+
10
+ attr_reader :algo, :target, :graph, :max_hits
11
+ attr_accessor :current_paths, :hits
12
+
13
+ def initialize(algo, graph, target, max_hits)
14
+ super()
15
+ @algo = algo
16
+ @max_hits = max_hits || -1
17
+ @graph = graph.blueprints_graph
18
+ @target = unwrap target if target
19
+ end
20
+
21
+ def processNextStart
22
+ next_raw_path.map do |e|
23
+ if e.is_a? Node
24
+ Neo4jVertex.new e, graph
25
+ elsif e.is_a? Relationship
26
+ Neo4jEdge.new e, graph
27
+ else
28
+ e
29
+ end
30
+ end
31
+ end
32
+
33
+ def next_raw_path
34
+ loop do
35
+ if current_paths
36
+ if hits == 0
37
+ self.current_paths = nil
38
+ elsif current_paths.hasNext
39
+ self.hits -= 1
40
+ return current_paths.next
41
+ else
42
+ self.current_paths = nil
43
+ end
44
+ else
45
+ self.hits = max_hits
46
+ self.current_paths = @algo.findAllPaths(next_raw, target).iterator
47
+ end
48
+ end
49
+ end
50
+
51
+ def next_raw
52
+ unwrap starts.next
53
+ end
54
+
55
+ def unwrap(vertex)
56
+ if vertex.respond_to? :element
57
+ vertex.element.raw_element
58
+ else
59
+ vertex.raw_element
60
+ end
61
+ end
62
+ end
63
+
64
+ class PathFromPathPipe < PathPipe
65
+ attr_writer :target
66
+
67
+ def initialize(algo, graph, max_hits)
68
+ super(algo, graph, nil, max_hits)
69
+ end
70
+
71
+ def next_raw
72
+ path = starts.next
73
+ self.target = unwrap path.to_a.last
74
+ unwrap path[0]
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,152 @@
1
+ module Pacer
2
+ module Neo4j
3
+ module Algo
4
+ # Uses the interface defined here:
5
+ # http://api.neo4j.org/1.8/org/neo4j/graphdb/Path.html
6
+ #
7
+ # Note that I have removed methods that I didn't understand, assuming they are internal.
8
+ class PathWrapper
9
+ import org.neo4j.graphdb.Node
10
+ import com.tinkerpop.blueprints.impls.neo4j.Neo4jVertex
11
+ import com.tinkerpop.blueprints.impls.neo4j.Neo4jEdge
12
+
13
+ attr_reader :graph, :raw_path
14
+
15
+ def initialize(path, graph)
16
+ @raw_path = path
17
+ @graph = graph
18
+ @gv = graph.v
19
+ @ge = graph.e
20
+ end
21
+
22
+ #Returns the end vertex of this path.
23
+ def end_v
24
+ wrap_vertex raw_path.endNode
25
+ end
26
+
27
+ # Iterates through both the vertices and edges of this path in order.
28
+ def path
29
+ raw_path.iterator.to_route.map(graph: graph, element_type: :mixed) do |e|
30
+ if e.is_a? Node
31
+ wrap_vertex e
32
+ else
33
+ wrap_edge e
34
+ end
35
+ end
36
+ end
37
+
38
+ def to_a
39
+ path.to_a
40
+ end
41
+
42
+ # Returns the last edge in this path.
43
+ def end_e
44
+ wrap_edge raw_path.lastRelationship
45
+ end
46
+
47
+ # Returns the length of this path.
48
+ def length
49
+ raw_path.length
50
+ end
51
+
52
+ # Returns all the vertices in this path starting from the start vertex going forward towards the end vertex.
53
+ def v
54
+ raw_path.nodes.map { |n| wrap_vertex n }.to_route based_on: @gv
55
+ end
56
+
57
+ # Returns all the edges in between the vertices which this path consists of.
58
+ def e
59
+ raw_path.relationships.map { |r| wrap_edge r }.to_route based_on @ge
60
+ end
61
+
62
+ # Returns all the vertices in this path in reversed order, i.e.
63
+ def reverse_v
64
+ raw_path.reverseNodes.map { |n| wrap_vertex n }.to_route based_on @gv
65
+ end
66
+
67
+ # Returns all the edges in between the vertices which this path consists of in reverse order, i.e.
68
+ def reverse_e
69
+ raw_path.reverseRelationships.map { |r| wrap_edge r }.to_route based_on @ge
70
+ end
71
+
72
+ # Returns the start vertex of this path.
73
+ def start_v
74
+ wrap_vertex raw_path.startNode
75
+ end
76
+
77
+ def to_s
78
+ "#{ start_v.inspect }-(#{length})->#{end_v.inspect}"
79
+ end
80
+
81
+ def inspect
82
+ "#<Path #{ to_s }>"
83
+ end
84
+
85
+ # skips route creation = faster but less features
86
+ def out_edges(*args)
87
+ end_v.out_edges(*args)
88
+ end
89
+
90
+ # skips route creation = faster but less features
91
+ def in_edges(*args)
92
+ end_v.in_edges(*args)
93
+ end
94
+
95
+ # skips route creation = faster but less features
96
+ def both_edges(*args)
97
+ end_v.both_edges(*args)
98
+ end
99
+
100
+ # skips route creation = faster but less features
101
+ def out_vertices(*args)
102
+ end_v.out_vertices(*args)
103
+ end
104
+
105
+ # skips route creation = faster but less features
106
+ def in_vertices(*args)
107
+ end_v.in_vertices(*args)
108
+ end
109
+
110
+ # skips route creation = faster but less features
111
+ def both_vertices(*args)
112
+ end_v.both_vertices(*args)
113
+ end
114
+
115
+
116
+ def out_e(*args)
117
+ end_v.out_e(*args)
118
+ end
119
+
120
+ def in_e(*args)
121
+ end_v.in_e(*args)
122
+ end
123
+
124
+ def both_e(*args)
125
+ end_v.both_e(*args)
126
+ end
127
+
128
+ def out(*args)
129
+ end_v.out(*args)
130
+ end
131
+
132
+ def in(*args)
133
+ end_v.in(*args)
134
+ end
135
+
136
+ def both(*args)
137
+ end_v.both(*args)
138
+ end
139
+
140
+ private
141
+
142
+ def wrap_vertex(v)
143
+ Pacer::Wrappers::VertexWrapper.new graph, Neo4jVertex.new(v, graph.blueprints_graph)
144
+ end
145
+
146
+ def wrap_edge(e)
147
+ Pacer::Wrappers::EdgeWrapper.new graph, Neo4jEdge.new(e, graph.blueprints_graph)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,47 @@
1
+ module Pacer
2
+ module Neo4j
3
+ module Algo
4
+ # Uses the interface defined here:
5
+ # http://api.neo4j.org/1.8/org/neo4j/graphdb/traversal/TraversalBranch.html
6
+ #
7
+ # Note that I have removed methods that I didn't understand, assuming they are internal.
8
+ class TraversalBranchWrapper < PathWrapper
9
+ # Returns whether or not the traversal should continue further along this branch.
10
+ def continues?
11
+ raw_path.continues
12
+ end
13
+
14
+ # Returns the number of edges this expansion source has expanded.
15
+ def num_expanded
16
+ raw_path.expanded
17
+ end
18
+
19
+ # Returns whether or not this branch (the Path representation of this
20
+ # branch at least) should be included in the result of this traversal,
21
+ # i.e. returned as one of the Paths from f.ex.
22
+ # TraversalDescription.traverse(org.neo4j.graphdb.Node...)
23
+ def included?
24
+ raw_path.includes
25
+ end
26
+
27
+ # The parent expansion source which created this TraversalBranch.
28
+ def parent
29
+ TraversalBranchWrapper.new raw_path.parent, graph
30
+ end
31
+
32
+ # Explicitly tell this branch to be pruned so that consecutive calls to #next() is guaranteed to return null.
33
+ def prune!
34
+ raw_path.prune
35
+ end
36
+
37
+ def to_s
38
+ "#{super} num_expanded: #{ num_expanded }#{ continues? ? ' continues' : '' }#{ included? ? ' included' : '' }"
39
+ end
40
+
41
+ def inspect
42
+ "#<TBR #{to_s}>"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,50 +1,82 @@
1
- require 'yaml'
2
-
3
1
  module Pacer
2
+ module Neo4j
3
+ class Graph < PacerGraph
4
+ # I'm not sure exactly what this impacts but if it is false, many Pacer tests fail.
5
+ #
6
+ # Presumably Neo4j is faster with it set to false.
7
+ def safe_transactions=(b)
8
+ blueprints_graph.setCheckElementsInTransaction b
9
+ end
10
+
11
+ def safe_transactions
12
+ blueprints_graph.getCheckElementsInTransaction
13
+ end
14
+
15
+ def key_index_cache(type, name, size = :undefined)
16
+ if size == :undefined
17
+ lucene_auto_index(type).getCacheCapacity name
18
+ else
19
+ lucene_auto_index(type).setCacheCapacity name, size
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def index_properties(type, filters)
26
+ filters.properties.select { |k, v| key_indices(type).include?(k) and not v.nil? }
27
+ end
4
28
 
5
- # Add 'static methods' to the Pacer namespace.
6
- class << self
7
- # Return a graph for the given path. Will create a graph if none exists at
8
- # that location. (The graph is only created if data is actually added to it).
9
- #
10
- # If the graph is opened from a path, it will be registered to be closed by
11
- # Ruby's at_exit callback, but if an already open graph is given, it will
12
- # not.
13
- #
14
- # Please note that Pacer turns on Neo4j's checkElementsInTransaction
15
- # feature by default. For some sort of performance improvement at
16
- # the expense of an odd consistency model within transactions that
17
- # require considerable more complexity in client code, you can use
18
- # `graph.setCheckElementsInTransaction(false)` to disable the
19
- # feature.
20
- def neo4j(path_or_graph, args = nil)
21
- neo = com.tinkerpop.blueprints.impls.neo4j.Neo4jGraph
22
- if path_or_graph.is_a? String
23
- path = File.expand_path(path_or_graph)
24
- open = proc do
25
- graph = Pacer.open_graphs[path]
26
- unless graph
27
- if args
28
- graph = neo.new(path, args.to_hash_map)
29
+ def build_query(type, filters)
30
+ indexed = index_properties type, filters
31
+ if indexed.any?
32
+ indexed.map do |k, v|
33
+ if v.is_a? Numeric
34
+ "#{k}:#{v}"
29
35
  else
30
- graph = neo.new(path)
36
+ s = encode_property(v)
37
+ if s.is_a? String and s =~ /\s/
38
+ %{#{k}:"#{s}"}
39
+ else
40
+ "#{k}:#{s}"
41
+ end
31
42
  end
32
- Pacer.open_graphs[path] = graph
33
- graph.setCheckElementsInTransaction true
34
- end
35
- graph
43
+ end.join " AND "
44
+ else
45
+ nil
36
46
  end
37
- shutdown = proc do |g|
38
- g.blueprints_graph.shutdown
39
- Pacer.open_graphs[path] = nil
47
+ end
48
+
49
+ def neo_graph
50
+ blueprints_graph.raw_graph
51
+ end
52
+
53
+ def lucene_auto_index(type)
54
+ if type == :vertex
55
+ neo_graph.index.getNodeAutoIndexer.getIndexInternal
56
+ elsif type == :edge
57
+ neo_graph.index.getRelationshipAutoIndexer.getIndexInternal
58
+ end
59
+ end
60
+
61
+ def indexed_route(element_type, filters, block)
62
+ if search_manual_indices
63
+ super
64
+ else
65
+ query = build_query(element_type, filters)
66
+ if query
67
+ route = lucene query, element_type: element_type
68
+ filters.remove_property_keys key_indices(element_type)
69
+ if filters.any?
70
+ Pacer::Route.property_filter(route, filters, block)
71
+ else
72
+ route
73
+ end
74
+ elsif filters.route_modules.any?
75
+ mod = filters.route_modules.shift
76
+ Pacer::Route.property_filter(mod.route(self), filters, block)
77
+ end
40
78
  end
41
- PacerGraph.new(Pacer::YamlEncoder, open, shutdown)
42
- else
43
- # Don't register the new graph so that it won't be automatically closed.
44
- PacerGraph.new Pacer::YamlEncoder, proc { neo.new(path_or_graph) }
45
79
  end
46
80
  end
47
81
  end
48
-
49
-
50
82
  end
@@ -0,0 +1,112 @@
1
+ module Pacer
2
+ module Neo4j
3
+ class Graph
4
+ def lucene(query, opts = {})
5
+ opts = { back: self, element_type: :vertex }.merge opts
6
+ chain_route(opts.merge(query: query,
7
+ filter: :lucene,
8
+ index: choose_index(opts)))
9
+ end
10
+
11
+ private
12
+
13
+ def choose_index(opts)
14
+ et = opts[:element_type]
15
+ idx = opts[:index]
16
+ case idx
17
+ when String, Symbol
18
+ index(idx, et).index.raw_index
19
+ when Pacer::Wrappers::IndexWrapper
20
+ idx.index.raw_index
21
+ when com.tinkerpop.blueprints.Index
22
+ idx.raw_index
23
+ else
24
+ lucene_auto_index(et)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+
31
+ module Filter
32
+ module LuceneFilter
33
+ import org.neo4j.index.lucene.QueryContext
34
+
35
+ attr_accessor :index, :query, :sort_by, :reverse_numeric, :sort_numeric, :sort_by_score, :top, :fast
36
+
37
+ def count(max = nil)
38
+ iter = query_result
39
+ c = iter.count
40
+ if c >= 0
41
+ c
42
+ elsif max
43
+ iter.inject(0) do |n, _|
44
+ if n == max
45
+ return :max
46
+ else
47
+ n + 1
48
+ end
49
+ end
50
+ else
51
+ iter.inject(0) { |n, _| n + 1 }
52
+ end
53
+ ensure
54
+ iter.close
55
+ end
56
+
57
+ def sort_by_score!
58
+ self.sort_by_score = true
59
+ self
60
+ end
61
+
62
+ def sort(*keys)
63
+ self.sort_by = keys
64
+ self
65
+ end
66
+
67
+ def top_hits(n)
68
+ self.top = n
69
+ self
70
+ end
71
+
72
+ def fast!
73
+ self.fast = true
74
+ self
75
+ end
76
+
77
+ protected
78
+
79
+ def build_query
80
+ qc = QueryContext.new(query)
81
+ qc = qc.tradeCorrectnessForSpeed if fast
82
+ qc = qc.top(top) if top
83
+ if sort_by_score
84
+ qc.sortByScore
85
+ elsif sort_by
86
+ qc.sort(*[*sort_by].map(&:to_s))
87
+ elsif sort_numeric
88
+ qc.sortNumeric(sort_numeric, false)
89
+ elsif reverse_numeric
90
+ qc.sortNumeric(reverse_numeric, true)
91
+ else
92
+ qc
93
+ end
94
+ end
95
+
96
+ def query_result
97
+ index.query build_query
98
+ end
99
+
100
+ def source_iterator
101
+ pipe = Pacer::Neo4j::RawVertexWrappingPipe.new graph
102
+ pipe.setStarts query_result
103
+ pipe.enablePath(true)
104
+ pipe
105
+ end
106
+
107
+ def inspect_string
108
+ "#{ inspect_class_name }(#{ query }) ~ #{ query_result.count }"
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,18 @@
1
+ module Pacer
2
+ module Neo4j
3
+ class RawVertexWrappingPipe < Pacer::Pipes::RubyPipe
4
+ import com.tinkerpop.blueprints.impls.neo4j.Neo4jVertex
5
+
6
+ attr_reader :graph
7
+
8
+ def initialize(graph)
9
+ super()
10
+ @graph = graph.blueprints_graph
11
+ end
12
+
13
+ def processNextStart
14
+ Neo4jVertex.new starts.next, graph
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,6 +1,6 @@
1
1
  module Pacer
2
2
  module Neo4j
3
- VERSION = "2.0.1"
3
+ VERSION = "2.1.0"
4
4
  JAR = "pacer-neo4j-#{ VERSION }-standalone.jar"
5
5
  JAR_PATH = "lib/#{ JAR }"
6
6
  BLUEPRINTS_VERSION = "2.1.0"
data/pom.xml CHANGED
@@ -7,7 +7,7 @@
7
7
  <artifactId>pacer-neo4j</artifactId>
8
8
  <!-- NOTE: the following properties are automatically updated based on the values in lib/pacer-neo4j/version.rb -->
9
9
  <properties>
10
- <gem.version>2.0.1</gem.version>
10
+ <gem.version>2.1.0</gem.version>
11
11
  <blueprints.version>2.1.0</blueprints.version>
12
12
  <pipes.version>2.1.0</pipes.version>
13
13
  </properties>
@@ -32,6 +32,11 @@
32
32
  <artifactId>blueprints-neo4j-graph</artifactId>
33
33
  <version>${blueprints.version}</version>
34
34
  </dependency>
35
+ <dependency>
36
+ <groupId>com.tinkerpop.blueprints</groupId>
37
+ <artifactId>blueprints-neo4jbatch-graph</artifactId>
38
+ <version>${blueprints.version}</version>
39
+ </dependency>
35
40
  </dependencies>
36
41
 
37
42
  <repositories>
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: pacer-neo4j
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 2.0.1
5
+ version: 2.1.0
6
6
  platform: java
7
7
  authors:
8
8
  - Darrick Wiebe
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-27 00:00:00.000000000 Z
12
+ date: 2012-10-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pacer
@@ -39,13 +39,22 @@ files:
39
39
  - README.md
40
40
  - Rakefile
41
41
  - lib/pacer-neo4j.rb
42
+ - lib/pacer-neo4j/algo.rb
43
+ - lib/pacer-neo4j/algo/block_cost_evaluator.rb
44
+ - lib/pacer-neo4j/algo/block_estimate_evaluator.rb
45
+ - lib/pacer-neo4j/algo/block_path_expander.rb
46
+ - lib/pacer-neo4j/algo/path_pipe.rb
47
+ - lib/pacer-neo4j/algo/path_wrapper.rb
48
+ - lib/pacer-neo4j/algo/traversal_branch_wrapper.rb
42
49
  - lib/pacer-neo4j/graph.rb
50
+ - lib/pacer-neo4j/lucene_filter.rb
51
+ - lib/pacer-neo4j/raw_vertex_wrapping_pipe.rb
43
52
  - lib/pacer-neo4j/rspec.rb
44
53
  - lib/pacer-neo4j/version.rb
45
54
  - pacer-neo4j.gemspec
46
55
  - pom.xml
47
56
  - pom/standalone.xml
48
- - lib/pacer-neo4j-2.0.1-standalone.jar
57
+ - lib/pacer-neo4j-2.1.0-standalone.jar
49
58
  homepage: http://neo4j.org
50
59
  licenses: []
51
60
  post_install_message: