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.
Files changed (99) hide show
  1. data/CHANGELOG +141 -0
  2. data/CONTRIBUTORS +17 -0
  3. data/Gemfile +16 -0
  4. data/README.rdoc +135 -0
  5. data/lib/generators/neo4j.rb +65 -0
  6. data/lib/generators/neo4j/model/model_generator.rb +39 -0
  7. data/lib/generators/neo4j/model/templates/model.erb +7 -0
  8. data/lib/neo4j.rb +77 -0
  9. data/lib/neo4j/config.rb +153 -0
  10. data/lib/neo4j/database.rb +56 -0
  11. data/lib/neo4j/equal.rb +21 -0
  12. data/lib/neo4j/event_handler.rb +116 -0
  13. data/lib/neo4j/index/class_methods.rb +62 -0
  14. data/lib/neo4j/index/index.rb +33 -0
  15. data/lib/neo4j/index/indexer.rb +312 -0
  16. data/lib/neo4j/index/indexer_registry.rb +68 -0
  17. data/lib/neo4j/index/lucene_query.rb +191 -0
  18. data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
  19. data/lib/neo4j/jars/lucene-core-3.0.2.jar +0 -0
  20. data/lib/neo4j/jars/neo4j-index-1.2-1.2.M03.jar +0 -0
  21. data/lib/neo4j/jars/neo4j-kernel-1.2-1.2.M03.jar +0 -0
  22. data/lib/neo4j/jars/neo4j-lucene-index-0.2-1.2.M03.jar +0 -0
  23. data/lib/neo4j/load.rb +21 -0
  24. data/lib/neo4j/mapping/class_methods/init_node.rb +50 -0
  25. data/lib/neo4j/mapping/class_methods/init_rel.rb +35 -0
  26. data/lib/neo4j/mapping/class_methods/list.rb +13 -0
  27. data/lib/neo4j/mapping/class_methods/property.rb +82 -0
  28. data/lib/neo4j/mapping/class_methods/relationship.rb +91 -0
  29. data/lib/neo4j/mapping/class_methods/rule.rb +295 -0
  30. data/lib/neo4j/mapping/decl_relationship_dsl.rb +214 -0
  31. data/lib/neo4j/mapping/has_list.rb +134 -0
  32. data/lib/neo4j/mapping/has_n.rb +83 -0
  33. data/lib/neo4j/mapping/node_mixin.rb +112 -0
  34. data/lib/neo4j/mapping/relationship_mixin.rb +120 -0
  35. data/lib/neo4j/model.rb +4 -0
  36. data/lib/neo4j/neo4j.rb +95 -0
  37. data/lib/neo4j/node.rb +131 -0
  38. data/lib/neo4j/node_mixin.rb +4 -0
  39. data/lib/neo4j/node_relationship.rb +149 -0
  40. data/lib/neo4j/node_traverser.rb +157 -0
  41. data/lib/neo4j/property.rb +111 -0
  42. data/lib/neo4j/rails/attributes.rb +155 -0
  43. data/lib/neo4j/rails/callbacks.rb +34 -0
  44. data/lib/neo4j/rails/finders.rb +134 -0
  45. data/lib/neo4j/rails/lucene_connection_closer.rb +19 -0
  46. data/lib/neo4j/rails/mapping/property.rb +60 -0
  47. data/lib/neo4j/rails/model.rb +105 -0
  48. data/lib/neo4j/rails/persistence.rb +260 -0
  49. data/lib/neo4j/rails/railtie.rb +21 -0
  50. data/lib/neo4j/rails/relationships/mapper.rb +96 -0
  51. data/lib/neo4j/rails/relationships/relationship.rb +30 -0
  52. data/lib/neo4j/rails/relationships/relationships.rb +60 -0
  53. data/lib/neo4j/rails/serialization.rb +25 -0
  54. data/lib/neo4j/rails/timestamps.rb +65 -0
  55. data/lib/neo4j/rails/transaction.rb +67 -0
  56. data/lib/neo4j/rails/tx_methods.rb +15 -0
  57. data/lib/neo4j/rails/validations.rb +38 -0
  58. data/lib/neo4j/rails/validations/non_nil.rb +11 -0
  59. data/lib/neo4j/rails/validations/uniqueness.rb +37 -0
  60. data/lib/neo4j/relationship.rb +169 -0
  61. data/lib/neo4j/relationship_mixin.rb +4 -0
  62. data/lib/neo4j/relationship_traverser.rb +92 -0
  63. data/lib/neo4j/to_java.rb +31 -0
  64. data/lib/neo4j/transaction.rb +68 -0
  65. data/lib/neo4j/type_converters.rb +117 -0
  66. data/lib/neo4j/version.rb +3 -0
  67. data/lib/orm_adapter/adapters/neo4j.rb +55 -0
  68. data/lib/tmp/neo4j/active_tx_log +1 -0
  69. data/lib/tmp/neo4j/index/lucene-store.db +0 -0
  70. data/lib/tmp/neo4j/index/lucene.log.active +0 -0
  71. data/lib/tmp/neo4j/lucene-fulltext/lucene-store.db +0 -0
  72. data/lib/tmp/neo4j/lucene-fulltext/lucene.log.active +0 -0
  73. data/lib/tmp/neo4j/lucene/lucene-store.db +0 -0
  74. data/lib/tmp/neo4j/lucene/lucene.log.active +0 -0
  75. data/lib/tmp/neo4j/messages.log +85 -0
  76. data/lib/tmp/neo4j/neostore +0 -0
  77. data/lib/tmp/neo4j/neostore.id +0 -0
  78. data/lib/tmp/neo4j/neostore.nodestore.db +0 -0
  79. data/lib/tmp/neo4j/neostore.nodestore.db.id +0 -0
  80. data/lib/tmp/neo4j/neostore.propertystore.db +0 -0
  81. data/lib/tmp/neo4j/neostore.propertystore.db.arrays +0 -0
  82. data/lib/tmp/neo4j/neostore.propertystore.db.arrays.id +0 -0
  83. data/lib/tmp/neo4j/neostore.propertystore.db.id +0 -0
  84. data/lib/tmp/neo4j/neostore.propertystore.db.index +0 -0
  85. data/lib/tmp/neo4j/neostore.propertystore.db.index.id +0 -0
  86. data/lib/tmp/neo4j/neostore.propertystore.db.index.keys +0 -0
  87. data/lib/tmp/neo4j/neostore.propertystore.db.index.keys.id +0 -0
  88. data/lib/tmp/neo4j/neostore.propertystore.db.strings +0 -0
  89. data/lib/tmp/neo4j/neostore.propertystore.db.strings.id +0 -0
  90. data/lib/tmp/neo4j/neostore.relationshipstore.db +0 -0
  91. data/lib/tmp/neo4j/neostore.relationshipstore.db.id +0 -0
  92. data/lib/tmp/neo4j/neostore.relationshiptypestore.db +0 -0
  93. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.id +0 -0
  94. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names +0 -0
  95. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names.id +0 -0
  96. data/lib/tmp/neo4j/nioneo_logical.log.active +0 -0
  97. data/lib/tmp/neo4j/tm_tx_log.1 +0 -0
  98. data/neo4j.gemspec +31 -0
  99. 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
+