neo4j 1.0.0.beta.21-java
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +141 -0
- data/CONTRIBUTORS +17 -0
- data/Gemfile +16 -0
- data/README.rdoc +135 -0
- data/lib/generators/neo4j.rb +65 -0
- data/lib/generators/neo4j/model/model_generator.rb +39 -0
- data/lib/generators/neo4j/model/templates/model.erb +7 -0
- data/lib/neo4j.rb +77 -0
- data/lib/neo4j/config.rb +153 -0
- data/lib/neo4j/database.rb +56 -0
- data/lib/neo4j/equal.rb +21 -0
- data/lib/neo4j/event_handler.rb +116 -0
- data/lib/neo4j/index/class_methods.rb +62 -0
- data/lib/neo4j/index/index.rb +33 -0
- data/lib/neo4j/index/indexer.rb +312 -0
- data/lib/neo4j/index/indexer_registry.rb +68 -0
- data/lib/neo4j/index/lucene_query.rb +191 -0
- data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
- data/lib/neo4j/jars/lucene-core-3.0.2.jar +0 -0
- data/lib/neo4j/jars/neo4j-index-1.2-1.2.M03.jar +0 -0
- data/lib/neo4j/jars/neo4j-kernel-1.2-1.2.M03.jar +0 -0
- data/lib/neo4j/jars/neo4j-lucene-index-0.2-1.2.M03.jar +0 -0
- data/lib/neo4j/load.rb +21 -0
- data/lib/neo4j/mapping/class_methods/init_node.rb +50 -0
- data/lib/neo4j/mapping/class_methods/init_rel.rb +35 -0
- data/lib/neo4j/mapping/class_methods/list.rb +13 -0
- data/lib/neo4j/mapping/class_methods/property.rb +82 -0
- data/lib/neo4j/mapping/class_methods/relationship.rb +91 -0
- data/lib/neo4j/mapping/class_methods/rule.rb +295 -0
- data/lib/neo4j/mapping/decl_relationship_dsl.rb +214 -0
- data/lib/neo4j/mapping/has_list.rb +134 -0
- data/lib/neo4j/mapping/has_n.rb +83 -0
- data/lib/neo4j/mapping/node_mixin.rb +112 -0
- data/lib/neo4j/mapping/relationship_mixin.rb +120 -0
- data/lib/neo4j/model.rb +4 -0
- data/lib/neo4j/neo4j.rb +95 -0
- data/lib/neo4j/node.rb +131 -0
- data/lib/neo4j/node_mixin.rb +4 -0
- data/lib/neo4j/node_relationship.rb +149 -0
- data/lib/neo4j/node_traverser.rb +157 -0
- data/lib/neo4j/property.rb +111 -0
- data/lib/neo4j/rails/attributes.rb +155 -0
- data/lib/neo4j/rails/callbacks.rb +34 -0
- data/lib/neo4j/rails/finders.rb +134 -0
- data/lib/neo4j/rails/lucene_connection_closer.rb +19 -0
- data/lib/neo4j/rails/mapping/property.rb +60 -0
- data/lib/neo4j/rails/model.rb +105 -0
- data/lib/neo4j/rails/persistence.rb +260 -0
- data/lib/neo4j/rails/railtie.rb +21 -0
- data/lib/neo4j/rails/relationships/mapper.rb +96 -0
- data/lib/neo4j/rails/relationships/relationship.rb +30 -0
- data/lib/neo4j/rails/relationships/relationships.rb +60 -0
- data/lib/neo4j/rails/serialization.rb +25 -0
- data/lib/neo4j/rails/timestamps.rb +65 -0
- data/lib/neo4j/rails/transaction.rb +67 -0
- data/lib/neo4j/rails/tx_methods.rb +15 -0
- data/lib/neo4j/rails/validations.rb +38 -0
- data/lib/neo4j/rails/validations/non_nil.rb +11 -0
- data/lib/neo4j/rails/validations/uniqueness.rb +37 -0
- data/lib/neo4j/relationship.rb +169 -0
- data/lib/neo4j/relationship_mixin.rb +4 -0
- data/lib/neo4j/relationship_traverser.rb +92 -0
- data/lib/neo4j/to_java.rb +31 -0
- data/lib/neo4j/transaction.rb +68 -0
- data/lib/neo4j/type_converters.rb +117 -0
- data/lib/neo4j/version.rb +3 -0
- data/lib/orm_adapter/adapters/neo4j.rb +55 -0
- data/lib/tmp/neo4j/active_tx_log +1 -0
- data/lib/tmp/neo4j/index/lucene-store.db +0 -0
- data/lib/tmp/neo4j/index/lucene.log.active +0 -0
- data/lib/tmp/neo4j/lucene-fulltext/lucene-store.db +0 -0
- data/lib/tmp/neo4j/lucene-fulltext/lucene.log.active +0 -0
- data/lib/tmp/neo4j/lucene/lucene-store.db +0 -0
- data/lib/tmp/neo4j/lucene/lucene.log.active +0 -0
- data/lib/tmp/neo4j/messages.log +85 -0
- data/lib/tmp/neo4j/neostore +0 -0
- data/lib/tmp/neo4j/neostore.id +0 -0
- data/lib/tmp/neo4j/neostore.nodestore.db +0 -0
- data/lib/tmp/neo4j/neostore.nodestore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.arrays +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.arrays.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index.keys +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index.keys.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.strings +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.strings.id +0 -0
- data/lib/tmp/neo4j/neostore.relationshipstore.db +0 -0
- data/lib/tmp/neo4j/neostore.relationshipstore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names.id +0 -0
- data/lib/tmp/neo4j/nioneo_logical.log.active +0 -0
- data/lib/tmp/neo4j/tm_tx_log.1 +0 -0
- data/neo4j.gemspec +31 -0
- metadata +216 -0
@@ -0,0 +1,312 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Index
|
3
|
+
class Indexer
|
4
|
+
attr_reader :indexer_for, :field_types, :via_relationships
|
5
|
+
|
6
|
+
def initialize(clazz, type) #:nodoc:
|
7
|
+
# part of the unique name of the index
|
8
|
+
@indexer_for = clazz
|
9
|
+
|
10
|
+
# do we want to index nodes or relationships ?
|
11
|
+
@type = type
|
12
|
+
|
13
|
+
@indexes = {} # key = type, value = java neo4j index
|
14
|
+
@field_types = {} # key = field, value = type (e.g. :exact or :fulltext)
|
15
|
+
@via_relationships = {} # key = field, value = relationship
|
16
|
+
|
17
|
+
# to enable subclass indexing to work properly, store a list of parent indexers and
|
18
|
+
# whenever an operation is performed on this one, perform it on all
|
19
|
+
@parent_indexers = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def inherit_fields_from(parent_index) #:nodoc:
|
23
|
+
return unless parent_index
|
24
|
+
@field_types.reverse_merge!(parent_index.field_types) if parent_index.respond_to?(:field_types)
|
25
|
+
@via_relationships.reverse_merge!(parent_index.via_relationships) if parent_index.respond_to?(:via_relationships)
|
26
|
+
@parent_indexers << parent_index
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"Indexer @#{object_id} [index_for:#{@indexer_for}, field_types=#{@field_types.keys.join(', ')}, via=#{@via_relationships.inspect}]"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Add an index on a field so that it will be automatically updated by neo4j transactional events.
|
34
|
+
#
|
35
|
+
# The index method takes an optional configuration hash which allows you to:
|
36
|
+
#
|
37
|
+
# === Add an index on an a property
|
38
|
+
#
|
39
|
+
# Example:
|
40
|
+
# class Person
|
41
|
+
# include Neo4j::NodeMixin
|
42
|
+
# index :name
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# When the property name is changed/deleted or the node created it will keep the lucene index in sync.
|
46
|
+
# You can then perform a lucene query like this: Person.find('name: andreas')
|
47
|
+
#'
|
48
|
+
# === Add index on other nodes.
|
49
|
+
#
|
50
|
+
# Example:
|
51
|
+
#
|
52
|
+
# class Person
|
53
|
+
# include Neo4j::NodeMixin
|
54
|
+
# has_n(:friends).to(Contact)
|
55
|
+
# has_n(:known_by).from(:friends)
|
56
|
+
# index :user_id, :via => :known_by
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# Notice that you *must* specify an incoming relationship with the via key, as shown above.
|
60
|
+
# In the example above an index <tt>user_id</tt> will be added to all Person nodes which has a <tt>friends</tt> relationship
|
61
|
+
# that person with that user_id. This allows you to do lucene queries on your friends properties.
|
62
|
+
#
|
63
|
+
# === Set the type value to index
|
64
|
+
# By default all values will be indexed as Strings.
|
65
|
+
# If you want for example to do a numerical range query you must tell Neo4j.rb to index it as a numeric value.
|
66
|
+
# You do that with the key <tt>type</tt> on the property.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
# class Person
|
70
|
+
# include Neo4j::NodeMixin
|
71
|
+
# property :weight, :type => Float
|
72
|
+
# index :weight
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# Supported values for <tt>:type</tt> is <tt>String</tt>, <tt>Float</tt>, <tt>Date</tt>, <tt>DateTime</tt> and <tt>Fixnum</tt>
|
76
|
+
#
|
77
|
+
# === For more information
|
78
|
+
# * See Neo4j::Index::LuceneQuery
|
79
|
+
# * See #find
|
80
|
+
#
|
81
|
+
def index(field, conf = {})
|
82
|
+
if conf[:via]
|
83
|
+
rel_dsl = @indexer_for._decl_rels[conf[:via]]
|
84
|
+
raise "No relationship defined for '#{conf[:via]}'. Check class '#{@indexer_for}': index :#{field}, via=>:#{conf[:via]} <-- error. Define it with a has_one or has_n" unless rel_dsl
|
85
|
+
raise "Only incoming relationship are possible to define index on. Check class '#{@indexer_for}': index :#{field}, via=>:#{conf[:via]}" unless rel_dsl.incoming?
|
86
|
+
via_indexer = rel_dsl.target_class._indexer
|
87
|
+
|
88
|
+
field = field.to_s
|
89
|
+
@via_relationships[field] = rel_dsl
|
90
|
+
conf.delete :via # avoid endless recursion
|
91
|
+
via_indexer.index(field, conf)
|
92
|
+
else
|
93
|
+
# raise "Already defined an (via?) index on #{field}, Using the same index for from two classes ? Check index :#{field}, :via => :#{@indexer_for}" if @field_types[field.to_s]
|
94
|
+
@field_types[field.to_s] = conf[:type] || :exact
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def remove_index_on_fields(node, props, tx_data) #:nodoc:
|
99
|
+
@field_types.keys.each { |field| rm_index(node, field, props[field]) if props[field] }
|
100
|
+
# remove all via indexed fields
|
101
|
+
@via_relationships.each_value do |dsl|
|
102
|
+
indexer = dsl.target_class._indexer
|
103
|
+
tx_data.deleted_relationships.each do |rel|
|
104
|
+
start_node = rel._start_node
|
105
|
+
next if node != rel._end_node
|
106
|
+
indexer.remove_index_on_fields(start_node, props, tx_data)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def update_on_deleted_relationship(relationship) #:nodoc:
|
112
|
+
update_on_relationship(relationship, false)
|
113
|
+
end
|
114
|
+
|
115
|
+
def update_on_new_relationship(relationship) #:nodoc:
|
116
|
+
update_on_relationship(relationship, true)
|
117
|
+
end
|
118
|
+
|
119
|
+
def update_on_relationship(relationship, is_created) #:nodoc:
|
120
|
+
rel_type = relationship.rel_type
|
121
|
+
end_node = relationship._end_node
|
122
|
+
# find which via relationship match rel_type
|
123
|
+
@via_relationships.each_pair do |field, dsl|
|
124
|
+
# have we declared an index on this changed relationship ?
|
125
|
+
next unless dsl.rel_type == rel_type
|
126
|
+
|
127
|
+
# yes, so find the node and value we should update the index on
|
128
|
+
val = end_node[field]
|
129
|
+
start_node = relationship._start_node
|
130
|
+
|
131
|
+
# find the indexer to use
|
132
|
+
indexer = dsl.target_class._indexer
|
133
|
+
|
134
|
+
# is the relationship created or deleted ?
|
135
|
+
if is_created
|
136
|
+
indexer.update_index_on(start_node, field, nil, val)
|
137
|
+
else
|
138
|
+
indexer.update_index_on(start_node, field, val, nil)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def update_index_on(node, field, old_val, new_val) #:nodoc:
|
144
|
+
if @via_relationships.include?(field)
|
145
|
+
dsl = @via_relationships[field]
|
146
|
+
target_class = dsl.target_class
|
147
|
+
|
148
|
+
dsl._all_relationships(node).each do |rel|
|
149
|
+
other = rel._start_node
|
150
|
+
target_class._indexer.update_single_index_on(other, field, old_val, new_val)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
update_single_index_on(node, field, old_val, new_val)
|
154
|
+
end
|
155
|
+
|
156
|
+
def update_single_index_on(node, field, old_val, new_val) #:nodoc:
|
157
|
+
if @field_types.include?(field)
|
158
|
+
rm_index(node, field, old_val) if old_val
|
159
|
+
add_index(node, field, new_val) if new_val
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns true if there is an index on the given field.
|
164
|
+
#
|
165
|
+
def index?(field)
|
166
|
+
@field_types.include?(field.to_s)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns the type of index for the given field (e.g. :exact or :fulltext)
|
170
|
+
#
|
171
|
+
def index_type_for(field) #:nodoc:
|
172
|
+
return nil unless index?(field)
|
173
|
+
@field_types[field.to_s]
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns true if there is an index of the given type defined.
|
177
|
+
def index_type?(type)
|
178
|
+
@field_types.values.include?(type)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Adds an index on the given entity
|
182
|
+
# This is normally not needed since you can instead declare an index which will automatically keep
|
183
|
+
# the lucene index in sync. See #index
|
184
|
+
#
|
185
|
+
def add_index(entity, field, value)
|
186
|
+
return false unless @field_types.has_key?(field)
|
187
|
+
|
188
|
+
# we might need to know what type the properties are when indexing and querying
|
189
|
+
@decl_props ||= @indexer_for.respond_to?(:_decl_props) && @indexer_for._decl_props
|
190
|
+
|
191
|
+
type = @decl_props && @decl_props[field.to_sym] && @decl_props[field.to_sym][:type]
|
192
|
+
if type
|
193
|
+
value = if String != type
|
194
|
+
org.neo4j.index.impl.lucene.ValueContext.new(value).indexNumeric
|
195
|
+
else
|
196
|
+
org.neo4j.index.impl.lucene.ValueContext.new(value)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
index_for_field(field.to_s).add(entity, field, value)
|
201
|
+
@parent_indexers.each { |i| i.add_index(entity, field, value) }
|
202
|
+
end
|
203
|
+
|
204
|
+
# Removes an index on the given entity
|
205
|
+
# This is normally not needed since you can instead declare an index which will automatically keep
|
206
|
+
# the lucene index in sync. See #index
|
207
|
+
#
|
208
|
+
def rm_index(entity, field, value)
|
209
|
+
return false unless @field_types.has_key?(field)
|
210
|
+
index_for_field(field).remove(entity, field, value)
|
211
|
+
@parent_indexers.each { |i| i.rm_index(entity, field, value) }
|
212
|
+
end
|
213
|
+
|
214
|
+
# Performs a Lucene Query.
|
215
|
+
#
|
216
|
+
# In order to use this you have to declare an index on the fields first, see #index.
|
217
|
+
# Notice that you should close the lucene query after the query has been executed.
|
218
|
+
# You can do that either by provide an block or calling the Neo4j::Index::LuceneQuery#close
|
219
|
+
# method. When performing queries from Ruby on Rails you do not need this since it will be automatically closed
|
220
|
+
# (by Rack).
|
221
|
+
#
|
222
|
+
# === Example, with a block
|
223
|
+
#
|
224
|
+
# Person.find('name: kalle') {|query| puts "#{[*query].join(', )"}
|
225
|
+
#
|
226
|
+
# ==== Example
|
227
|
+
#
|
228
|
+
# query = Person.find('name: kalle')
|
229
|
+
# puts "First item #{query.first}"
|
230
|
+
# query.close
|
231
|
+
#
|
232
|
+
# === Return Value
|
233
|
+
# It will return a Neo4j::Index::LuceneQuery object
|
234
|
+
#
|
235
|
+
#
|
236
|
+
def find(query, params = {})
|
237
|
+
# we might need to know what type the properties are when indexing and querying
|
238
|
+
@decl_props ||= @indexer_for.respond_to?(:_decl_props) && @indexer_for._decl_props
|
239
|
+
|
240
|
+
index = index_for_type(params[:type] || :exact)
|
241
|
+
query = (params[:wrapped].nil? || params[:wrapped]) ? LuceneQuery.new(index, @decl_props, query) : index.query(query)
|
242
|
+
|
243
|
+
if block_given?
|
244
|
+
begin
|
245
|
+
ret = yield query
|
246
|
+
ensure
|
247
|
+
query.close
|
248
|
+
end
|
249
|
+
ret
|
250
|
+
else
|
251
|
+
query
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# delete the index, if no type is provided clear all types of indexes
|
256
|
+
def delete_index_type(type=nil)
|
257
|
+
if type
|
258
|
+
#raise "can't clear index of type '#{type}' since it does not exist ([#{@field_types.values.join(',')}] exists)" unless index_type?(type)
|
259
|
+
@indexes[type] && @indexes[type].delete
|
260
|
+
@indexes[type] = nil
|
261
|
+
else
|
262
|
+
@indexes.each_value { |index| index.delete }
|
263
|
+
@indexes.clear
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def on_neo4j_shutdown #:nodoc:
|
268
|
+
# Since we might start the database again we must make sure that we don't keep any references to
|
269
|
+
# an old lucene index in memory.
|
270
|
+
@indexes.clear
|
271
|
+
end
|
272
|
+
|
273
|
+
# Removes the cached lucene index, can be useful for some RSpecs which needs to restart the Neo4j.
|
274
|
+
#
|
275
|
+
def rm_field_type(type=nil)
|
276
|
+
if type
|
277
|
+
@field_types.delete_if { |k, v| v == type }
|
278
|
+
else
|
279
|
+
@field_types.clear
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def index_for_field(field) #:nodoc:
|
284
|
+
type = @field_types[field]
|
285
|
+
@indexes[type] ||= create_index_with(type)
|
286
|
+
end
|
287
|
+
|
288
|
+
def index_for_type(type) #:nodoc:
|
289
|
+
@indexes[type] ||= create_index_with(type)
|
290
|
+
end
|
291
|
+
|
292
|
+
def lucene_config(type) #:nodoc:
|
293
|
+
conf = Neo4j::Config[:lucene][type.to_sym]
|
294
|
+
raise "unknown lucene type #{type}" unless conf
|
295
|
+
conf
|
296
|
+
end
|
297
|
+
|
298
|
+
def create_index_with(type) #:nodoc:
|
299
|
+
db=Neo4j.started_db
|
300
|
+
index_config = lucene_config(type)
|
301
|
+
if @type == :node
|
302
|
+
db.lucene.for_nodes("#{@indexer_for}-#{type}", index_config)
|
303
|
+
else
|
304
|
+
db.lucene.for_relationships("#{@indexer_for}-#{type}", index_config)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Index
|
3
|
+
class IndexerRegistry #:nodoc:
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def delete_all_indexes
|
7
|
+
@@indexers.values.each {|i| i.delete_index_type}
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_for(this_clazz, using_other_clazz, type)
|
11
|
+
@@indexers ||= {}
|
12
|
+
index = Indexer.new(this_clazz, type)
|
13
|
+
index.inherit_fields_from(@@indexers[using_other_clazz.to_s])
|
14
|
+
@@indexers[this_clazz.to_s] = index
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_by_class(classname)
|
18
|
+
@@indexers[classname]
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_node_deleted(node, old_props, tx_data)
|
22
|
+
indexer = find_by_class(old_props['_classname'] || node.class.to_s)
|
23
|
+
indexer && indexer.remove_index_on_fields(node, old_props, tx_data)
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_property_changed(node, field, old_val, new_val)
|
27
|
+
classname = node['_classname'] || node.class.to_s
|
28
|
+
indexer = find_by_class(classname)
|
29
|
+
indexer && indexer.update_index_on(node, field, old_val, new_val)
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_rel_property_changed(rel, field, old_val, new_val)
|
33
|
+
# works exactly like for nodes
|
34
|
+
on_property_changed(rel, field, old_val, new_val)
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_relationship_created(rel, tx_data)
|
38
|
+
end_node = rel._end_node
|
39
|
+
# if end_node was created in this transaction then it will be handled in on_property_changed
|
40
|
+
created = tx_data.created_nodes.find{|n| n.neo_id == end_node.neo_id}
|
41
|
+
unless created
|
42
|
+
indexer = find_by_class(end_node['_classname'])
|
43
|
+
indexer && indexer.update_on_new_relationship(rel)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_relationship_deleted(rel, old_props, tx_data)
|
48
|
+
on_node_deleted(rel, old_props, tx_data)
|
49
|
+
# if only the relationship has been deleted then we have to remove the index
|
50
|
+
# if both the relationship and the node has been deleted then the index will be removed in the
|
51
|
+
# on_node_deleted callback
|
52
|
+
end_node = rel._end_node
|
53
|
+
deleted = tx_data.deleted_nodes.find{|n| n.neo_id == end_node.neo_id}
|
54
|
+
unless deleted
|
55
|
+
indexer = find_by_class(end_node['_classname'])
|
56
|
+
indexer && indexer.update_on_deleted_relationship(rel)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def on_neo4j_shutdown(*)
|
61
|
+
@@indexers.each_value {|indexer| indexer.on_neo4j_shutdown}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
Neo4j.unstarted_db.event_handler.add(IndexerRegistry)
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Index
|
3
|
+
# == LuceneQuery
|
4
|
+
#
|
5
|
+
# This object is returned when you call the #find method on the Node, Relationship.
|
6
|
+
# The actual query is not executed until the first item is requested.
|
7
|
+
#
|
8
|
+
# You can perform a query in many different ways:
|
9
|
+
#
|
10
|
+
# ==== By Hash
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# Person.find(:name => 'foo', :age => 3)
|
14
|
+
#
|
15
|
+
# ==== By Range
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
# Person.find(:age).between(15,35)
|
19
|
+
#
|
20
|
+
# ==== By Lucene Query Syntax
|
21
|
+
#
|
22
|
+
# Example
|
23
|
+
# Car.find('wheels:"4" AND colour: "blue")
|
24
|
+
#
|
25
|
+
# For more information about the syntax see http://lucene.apache.org/java/3_0_2/queryparsersyntax.html
|
26
|
+
#
|
27
|
+
# ==== By Compound Queries
|
28
|
+
#
|
29
|
+
# You can combine several queries by <tt>AND</tt>ing those together.
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
# Vehicle.find(:weight).between(5.0, 100000.0).and(:name).between('a', 'd')
|
33
|
+
#
|
34
|
+
# === See Also
|
35
|
+
# * Neo4j::Index::Indexer#index
|
36
|
+
# * Neo4j::Index::Indexer#find - which returns an LuceneQuery
|
37
|
+
#
|
38
|
+
class LuceneQuery
|
39
|
+
include Enumerable
|
40
|
+
attr_accessor :left_and_query, :left_or_query
|
41
|
+
|
42
|
+
def initialize(index, decl_props, query)
|
43
|
+
@index = index
|
44
|
+
@query = query
|
45
|
+
@decl_props = decl_props
|
46
|
+
end
|
47
|
+
|
48
|
+
# Since we include the Ruby Enumerable mixin we need this method.
|
49
|
+
def each
|
50
|
+
hits.each { |n| yield n.wrapper }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Close hits
|
54
|
+
#
|
55
|
+
# Closes the underlying search result. This method should be called whenever you've got what you wanted from the result and won't use it anymore.
|
56
|
+
# It's necessary to call it so that underlying indexes can dispose of allocated resources for this search result.
|
57
|
+
# You can however skip to call this method if you loop through the whole result, then close() will be called automatically.
|
58
|
+
# Even if you loop through the entire result and then call this method it will silently ignore any consequtive call (for convenience).
|
59
|
+
#
|
60
|
+
# This must be done according to the Neo4j Java Documentation:
|
61
|
+
def close
|
62
|
+
@hits.close if @hits
|
63
|
+
end
|
64
|
+
|
65
|
+
# True if there is no search hits.
|
66
|
+
def empty?
|
67
|
+
hits.size == 0
|
68
|
+
end
|
69
|
+
|
70
|
+
# returns the n'th search item
|
71
|
+
# Does simply loop all search items till the n'th is found.
|
72
|
+
#
|
73
|
+
def [](index)
|
74
|
+
each_with_index {|node,i| break node if index == i}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the number of search hits
|
78
|
+
def size
|
79
|
+
hits.size
|
80
|
+
end
|
81
|
+
|
82
|
+
def hits #:nodoc:
|
83
|
+
@hits ||= perform_query
|
84
|
+
end
|
85
|
+
|
86
|
+
# Performs a range query
|
87
|
+
# Notice that if you don't specify a type when declaring a property a String range query will be performed.
|
88
|
+
#
|
89
|
+
def between(lower, upper, lower_incusive=false, upper_inclusive=false)
|
90
|
+
raise "Expected a symbol. Syntax for range queries example: index(:weight).between(a,b)" unless Symbol === @query
|
91
|
+
raise "Can't only do range queries on Neo4j::NodeMixin, Neo4j::Model, Neo4j::RelationshipMixin" unless @decl_props
|
92
|
+
# check that we perform a range query on the same values as we have declared with the property :key, :type => ...
|
93
|
+
type = @decl_props[@query] && @decl_props[@query][:type]
|
94
|
+
raise "find(#{@query}).between(#{lower}, #{upper}): #{lower} not a #{type}" if type && !type === lower.class
|
95
|
+
raise "find(#{@query}).between(#{lower}, #{upper}): #{upper} not a #{type}" if type && !type === upper.class
|
96
|
+
|
97
|
+
# Make it possible to convert those values
|
98
|
+
lower = TypeConverters.convert(lower)
|
99
|
+
upper = TypeConverters.convert(upper)
|
100
|
+
|
101
|
+
@query = case lower
|
102
|
+
when Fixnum
|
103
|
+
org.apache.lucene.search.NumericRangeQuery.new_long_range(@query.to_s, lower, upper, lower_incusive, upper_inclusive)
|
104
|
+
when Float
|
105
|
+
org.apache.lucene.search.NumericRangeQuery.new_double_range(@query.to_s, lower, upper, lower_incusive, upper_inclusive)
|
106
|
+
else
|
107
|
+
org.apache.lucene.search.TermRangeQuery.new(@query.to_s, lower, upper, lower_incusive, upper_inclusive)
|
108
|
+
end
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create a compound lucene query.
|
113
|
+
#
|
114
|
+
# ==== Parameters
|
115
|
+
# query2 :: the query that should be AND together
|
116
|
+
#
|
117
|
+
# ==== Example
|
118
|
+
#
|
119
|
+
# Person.find(:name=>'kalle').and(:age => 3)
|
120
|
+
#
|
121
|
+
def and(query2)
|
122
|
+
new_query = LuceneQuery.new(@index, @decl_props, query2)
|
123
|
+
new_query.left_and_query = self
|
124
|
+
new_query
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# Sort descending the given fields.
|
129
|
+
def desc(*fields)
|
130
|
+
@order = fields.inject(@order || {}) { |memo, field| memo[field] = true; memo }
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
# Sort ascending the given fields.
|
135
|
+
def asc(*fields)
|
136
|
+
@order = fields.inject(@order || {}) { |memo, field| memo[field] = false; memo }
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def build_and_query(query) #:nodoc:
|
141
|
+
left_query = @left_and_query.build_query
|
142
|
+
and_query = org.apache.lucene.search.BooleanQuery.new
|
143
|
+
and_query.add(left_query, org.apache.lucene.search.BooleanClause::Occur::MUST)
|
144
|
+
and_query.add(query, org.apache.lucene.search.BooleanClause::Occur::MUST)
|
145
|
+
and_query
|
146
|
+
end
|
147
|
+
|
148
|
+
def build_sort_query(query) #:nodoc:
|
149
|
+
java_sort_fields = @order.keys.inject([]) do |memo, field|
|
150
|
+
decl_type = @decl_props && @decl_props[field] && @decl_props[field][:type]
|
151
|
+
type = case
|
152
|
+
when Float == decl_type
|
153
|
+
org.apache.lucene.search.SortField::DOUBLE
|
154
|
+
when Fixnum == decl_type
|
155
|
+
org.apache.lucene.search.SortField::LONG
|
156
|
+
else
|
157
|
+
org.apache.lucene.search.SortField::STRING
|
158
|
+
end
|
159
|
+
memo << org.apache.lucene.search.SortField.new(field.to_s, type, @order[field])
|
160
|
+
end
|
161
|
+
sort = org.apache.lucene.search.Sort.new(*java_sort_fields)
|
162
|
+
org.neo4j.index.impl.lucene.QueryContext.new(query).sort(sort)
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_hash_query(query) #:nodoc:
|
166
|
+
and_query = org.apache.lucene.search.BooleanQuery.new
|
167
|
+
|
168
|
+
query.each_pair do |key, value|
|
169
|
+
raise "Only String values valid in find(hash) got :#{key} => #{value} which is not a String" if !value.is_a?(String) && @decl_props[key] && @decl_props[key][:type] != String
|
170
|
+
term = org.apache.lucene.index.Term.new(key.to_s, value.to_s)
|
171
|
+
term_query = org.apache.lucene.search.TermQuery.new(term)
|
172
|
+
and_query.add(term_query, org.apache.lucene.search.BooleanClause::Occur::MUST)
|
173
|
+
end
|
174
|
+
and_query
|
175
|
+
end
|
176
|
+
|
177
|
+
def build_query #:nodoc:
|
178
|
+
query = @query
|
179
|
+
query = build_hash_query(query) if Hash === query
|
180
|
+
query = build_and_query(query) if @left_and_query
|
181
|
+
query = build_sort_query(query) if @order
|
182
|
+
query
|
183
|
+
end
|
184
|
+
|
185
|
+
def perform_query #:nodoc:
|
186
|
+
@index.query(build_query)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|