pacer-neo4j2 2.0.1-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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/README.md +23 -0
- data/Rakefile +35 -0
- data/lib/pacer-neo4j2/algo/block_cost_evaluator.rb +38 -0
- data/lib/pacer-neo4j2/algo/block_estimate_evaluator.rb +32 -0
- data/lib/pacer-neo4j2/algo/block_path_expander.rb +61 -0
- data/lib/pacer-neo4j2/algo/cypher_transform.rb +129 -0
- data/lib/pacer-neo4j2/algo/path_pipe.rb +79 -0
- data/lib/pacer-neo4j2/algo/path_wrapper.rb +134 -0
- data/lib/pacer-neo4j2/algo/traversal_branch_wrapper.rb +47 -0
- data/lib/pacer-neo4j2/algo/wrapping.rb +34 -0
- data/lib/pacer-neo4j2/algo.rb +406 -0
- data/lib/pacer-neo4j2/blueprints_graph.rb +42 -0
- data/lib/pacer-neo4j2/graph.rb +243 -0
- data/lib/pacer-neo4j2/lucene_filter.rb +112 -0
- data/lib/pacer-neo4j2/raw_vertex_wrapping_pipe.rb +18 -0
- data/lib/pacer-neo4j2/rspec.rb +39 -0
- data/lib/pacer-neo4j2/transaction_event_handler.rb +40 -0
- data/lib/pacer-neo4j2/tx_data_wrapper.rb +101 -0
- data/lib/pacer-neo4j2/version.rb +9 -0
- data/lib/pacer-neo4j2-2.0.1-standalone.jar +0 -0
- data/lib/pacer-neo4j2.rb +99 -0
- data/pacer-neo4j2.gemspec +22 -0
- data/pom/standalone.xml +22 -0
- data/pom.xml +95 -0
- metadata +84 -0
@@ -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::Neo4j2::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::Neo4j2::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::Neo4j2::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 and 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 and 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 = e.add DynamicRelationshipType.withName(label.to_s), Direction::OUTGOING
|
362
|
+
end
|
363
|
+
[*in_labels].each do |label|
|
364
|
+
e = e.add DynamicRelationshipType.withName(label.to_s), Direction::INCOMING
|
365
|
+
end
|
366
|
+
[*both_labels].each do |label|
|
367
|
+
e = 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,42 @@
|
|
1
|
+
module Pacer
|
2
|
+
module Neo4j2
|
3
|
+
class BlueprintsGraph < com.tinkerpop.blueprints.impls.neo4j2.Neo4j2Graph
|
4
|
+
attr_accessor :allow_auto_tx, :allow_auto_read_tx
|
5
|
+
|
6
|
+
# Threadlocal tx_depth is set in Pacer's graph_transaction_mixin.rb
|
7
|
+
def tx_depth
|
8
|
+
graphs = Thread.current[:graphs] ||= {}
|
9
|
+
tgi = graphs[object_id] ||= {}
|
10
|
+
tgi[:tx_depth] || 0
|
11
|
+
end
|
12
|
+
|
13
|
+
# Threadlocal read_tx_depth is set in Pacer's graph_transaction_mixin.rb
|
14
|
+
def read_tx_depth
|
15
|
+
graphs = Thread.current[:graphs] ||= {}
|
16
|
+
tgi = graphs[object_id] ||= {}
|
17
|
+
depth = tgi[:read_tx_depth] || 0
|
18
|
+
if depth == 0
|
19
|
+
tgi[:tx_depth] || 0 # Reads are allowed in any type of tx.
|
20
|
+
else
|
21
|
+
depth
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def autoStartTransaction(for_write)
|
26
|
+
if for_write
|
27
|
+
if allow_auto_tx or tx_depth != 0
|
28
|
+
super
|
29
|
+
else
|
30
|
+
raise Pacer::TransactionError, "Can't mutate the graph outside a transaction block"
|
31
|
+
end
|
32
|
+
else
|
33
|
+
if allow_auto_read_tx or read_tx_depth != 0
|
34
|
+
super
|
35
|
+
else
|
36
|
+
raise Pacer::TransactionError, "Can't read the graph outside a transaction or read_transaction block"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Pacer
|
4
|
+
module Neo4j2
|
5
|
+
class Graph < PacerGraph
|
6
|
+
JDate = java.util.Date
|
7
|
+
import java.text.SimpleDateFormat
|
8
|
+
|
9
|
+
# I'm not sure exactly what this impacts but if it is false, many Pacer tests fail.
|
10
|
+
#
|
11
|
+
# Presumably Neo4j is faster with it set to false.
|
12
|
+
def safe_transactions=(b)
|
13
|
+
blueprints_graph.setCheckElementsInTransaction b
|
14
|
+
end
|
15
|
+
|
16
|
+
def safe_transactions
|
17
|
+
blueprints_graph.getCheckElementsInTransaction
|
18
|
+
end
|
19
|
+
|
20
|
+
def allow_auto_tx=(b)
|
21
|
+
blueprints_graph.allow_auto_tx = b
|
22
|
+
end
|
23
|
+
|
24
|
+
def allow_auto_tx
|
25
|
+
blueprints_graph.allow_auto_tx
|
26
|
+
end
|
27
|
+
|
28
|
+
def allow_auto_read_tx=(b)
|
29
|
+
blueprints_graph.allow_auto_read_tx = b
|
30
|
+
end
|
31
|
+
|
32
|
+
def allow_auto_read_tx
|
33
|
+
blueprints_graph.allow_auto_read_tx
|
34
|
+
end
|
35
|
+
|
36
|
+
def cypher(query)
|
37
|
+
[query].to_route(element_type: :string, graph: self).cypher
|
38
|
+
end
|
39
|
+
|
40
|
+
def key_index_cache(type, name, size = :undefined)
|
41
|
+
indexer = lucene_auto_index(type)
|
42
|
+
if size == :undefined
|
43
|
+
indexer.getCacheCapacity name
|
44
|
+
else
|
45
|
+
indexer.setCacheCapacity name, size
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# When a Neo4J graph is restarted, the ids of any elements that were deleted
|
50
|
+
# will be reused. Running this code immediately after starting the graph
|
51
|
+
# prevents Neo4J from reusing those IDs.
|
52
|
+
def prevent_id_reuse!
|
53
|
+
{
|
54
|
+
edges: prevent_edge_id_reuse!,
|
55
|
+
vertices: prevent_vertex_id_reuse!
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# This works by simply creating IDs until the ID of a new element is greater than
|
60
|
+
# either the max existing ID, or the min_new_id argument.
|
61
|
+
def prevent_vertex_id_reuse!(min_new_id = nil)
|
62
|
+
min_new_id ||= v.element_ids.max
|
63
|
+
return unless min_new_id
|
64
|
+
g = blueprints_graph
|
65
|
+
n = 0
|
66
|
+
transaction do |_, rollback|
|
67
|
+
begin
|
68
|
+
n += 1
|
69
|
+
v = g.addVertex(nil)
|
70
|
+
end while v.getId < min_new_id
|
71
|
+
rollback.call
|
72
|
+
end
|
73
|
+
n
|
74
|
+
end
|
75
|
+
|
76
|
+
def prevent_edge_id_reuse!(min_new_id = nil)
|
77
|
+
min_new_id ||= e.element_ids.max
|
78
|
+
return unless min_new_id
|
79
|
+
g = blueprints_graph
|
80
|
+
n = 0
|
81
|
+
transaction do |_, rollback|
|
82
|
+
v1 = g.addVertex nil
|
83
|
+
v2 = g.addVertex nil
|
84
|
+
begin
|
85
|
+
n += 1
|
86
|
+
e = g.addEdge(nil, v1, v2, "temp")
|
87
|
+
end while e.getId < min_new_id
|
88
|
+
rollback.call
|
89
|
+
end
|
90
|
+
n
|
91
|
+
end
|
92
|
+
|
93
|
+
def neo_graph
|
94
|
+
blueprints_graph.raw_graph
|
95
|
+
end
|
96
|
+
|
97
|
+
def reopen_read_transaction
|
98
|
+
blueprints_graph.autoStartTransaction(false) if in_read_transaction?
|
99
|
+
end
|
100
|
+
|
101
|
+
def on_commit(&block)
|
102
|
+
return unless block
|
103
|
+
TransactionEventHandler.new(self).tap do |h|
|
104
|
+
h.on_commit = block
|
105
|
+
neo_graph.registerTransactionEventHandler h
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# This is actually only called if the commit fails and then it internally tries to
|
110
|
+
# rollback. It seems that it's actually possible for it to fail to rollback here, too...
|
111
|
+
#
|
112
|
+
# An exception in before_commit can definitely trigger this.
|
113
|
+
#
|
114
|
+
# Regular rollbacks do not get seen by the transaction system and no callback happens.
|
115
|
+
def on_commit_failed(&block)
|
116
|
+
return unless block
|
117
|
+
TransactionEventHandler.new(self).tap do |h|
|
118
|
+
h.on_commit_failed = block
|
119
|
+
neo_graph.registerTransactionEventHandler h
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def before_commit(&block)
|
124
|
+
return unless block
|
125
|
+
TransactionEventHandler.new(self).tap do |h|
|
126
|
+
h.before_commit = block
|
127
|
+
neo_graph.registerTransactionEventHandler h
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def drop_handler(h)
|
132
|
+
neo_graph.unregisterTransactionEventHandler h
|
133
|
+
end
|
134
|
+
|
135
|
+
# Creates a Blueprints key index without doing a rebuild.
|
136
|
+
def create_key_index_fast(name, type = :vertex)
|
137
|
+
raise "Invalid index type #{ type }" unless [:vertex, :edge].include? type
|
138
|
+
keys = (key_indices(type) + [name.to_s]).to_a
|
139
|
+
neo_settings = neo_graph.getNodeManager.getGraphProperties
|
140
|
+
iz = neo_graph.index.getNodeAutoIndexer
|
141
|
+
prop = ((type == :vertex) ? "Vertex:indexed_keys" : "Edge:indexed_keys")
|
142
|
+
transaction do
|
143
|
+
create_vertex.delete! # this forces Blueprints to actually start the transaction
|
144
|
+
neo_settings.setProperty prop, keys.to_java(:string)
|
145
|
+
keys.each do |key|
|
146
|
+
iz.startAutoIndexingProperty key
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def index_properties(type, filters)
|
155
|
+
filters.properties.select { |k, v| key_indices(type).include?(k) and not v.nil? }
|
156
|
+
end
|
157
|
+
|
158
|
+
def lucene_set(k, v)
|
159
|
+
statements = v.map { |x| "#{k}:#{lucene_value(x)}" }
|
160
|
+
"(#{ statements.join(' OR ') })"
|
161
|
+
end
|
162
|
+
|
163
|
+
def lucene_range(k, v)
|
164
|
+
if v.min and v.max
|
165
|
+
encoded = encode_property(v.min)
|
166
|
+
if encoded.is_a? JDate
|
167
|
+
"#{k}:[#{lucene_value v.min} TO #{lucene_value v.max}]"
|
168
|
+
else
|
169
|
+
"#{k}:[#{lucene_value v.min} TO #{lucene_value v.max}]"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def build_query(type, filters)
|
175
|
+
indexed = index_properties type, filters
|
176
|
+
if indexed.any?
|
177
|
+
indexed.map do |k, v|
|
178
|
+
k = k.to_s.gsub '/', '\\/'
|
179
|
+
if v.is_a? Range
|
180
|
+
lucene_range(k, v)
|
181
|
+
elsif v.class.name == 'RangeSet'
|
182
|
+
s = v.ranges.map { |r| lucene_range(k, r) }.join " OR "
|
183
|
+
"(#{s})"
|
184
|
+
elsif v.is_a? Set
|
185
|
+
lucene_set(k, v)
|
186
|
+
else
|
187
|
+
"#{k}:#{lucene_value v}"
|
188
|
+
end
|
189
|
+
end.compact.join " AND "
|
190
|
+
else
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def lucene_value(v)
|
196
|
+
s = encode_property(v)
|
197
|
+
if s.is_a? JDate
|
198
|
+
f = SimpleDateFormat.new 'yyyyMMddHHmmssSSS'
|
199
|
+
f.format s
|
200
|
+
elsif s
|
201
|
+
if s.is_a? String
|
202
|
+
s.inspect
|
203
|
+
else s
|
204
|
+
s
|
205
|
+
end
|
206
|
+
else
|
207
|
+
'NULL'
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def lucene_auto_index(type)
|
212
|
+
if type == :vertex
|
213
|
+
indexer = neo_graph.index.getNodeAutoIndexer
|
214
|
+
elsif type == :edge
|
215
|
+
indexer = neo_graph.index.getRelationshipAutoIndexer
|
216
|
+
end
|
217
|
+
indexer.getAutoIndex
|
218
|
+
end
|
219
|
+
|
220
|
+
def indexed_route(element_type, filters, block)
|
221
|
+
if search_manual_indices
|
222
|
+
super
|
223
|
+
else
|
224
|
+
filters.graph = self
|
225
|
+
filters.use_lookup!
|
226
|
+
query = build_query(element_type, filters)
|
227
|
+
if query
|
228
|
+
route = lucene query, element_type: element_type, extensions: filters.extensions, wrapper: filters.wrapper
|
229
|
+
filters.remove_property_keys key_indices(element_type)
|
230
|
+
if filters.any?
|
231
|
+
Pacer::Route.property_filter(route, filters, block)
|
232
|
+
else
|
233
|
+
route
|
234
|
+
end
|
235
|
+
elsif filters.route_modules.any?
|
236
|
+
mod = filters.route_modules.shift
|
237
|
+
Pacer::Route.property_filter(mod.route(self), filters, block)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|