neo4j 1.0.0.beta.21-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/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
|
+
|