pacer-neo4j 2.0.1-java → 2.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
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: