neo4j 1.0.0.beta.21-java

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