neo4j-core 0.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/Gemfile +27 -0
  2. data/README.rdoc +27 -0
  3. data/config/neo4j/config.yml +102 -0
  4. data/lib/db/active_tx_log +1 -0
  5. data/lib/db/index/lucene-store.db +0 -0
  6. data/lib/db/index/lucene.log.1 +0 -0
  7. data/lib/db/index/lucene.log.active +0 -0
  8. data/lib/db/lock +0 -0
  9. data/lib/db/messages.log +530 -0
  10. data/lib/db/neostore +0 -0
  11. data/lib/db/neostore.id +0 -0
  12. data/lib/db/neostore.nodestore.db +0 -0
  13. data/lib/db/neostore.nodestore.db.id +0 -0
  14. data/lib/db/neostore.propertystore.db +0 -0
  15. data/lib/db/neostore.propertystore.db.arrays +0 -0
  16. data/lib/db/neostore.propertystore.db.arrays.id +0 -0
  17. data/lib/db/neostore.propertystore.db.id +0 -0
  18. data/lib/db/neostore.propertystore.db.index +0 -0
  19. data/lib/db/neostore.propertystore.db.index.id +0 -0
  20. data/lib/db/neostore.propertystore.db.index.keys +0 -0
  21. data/lib/db/neostore.propertystore.db.index.keys.id +0 -0
  22. data/lib/db/neostore.propertystore.db.strings +0 -0
  23. data/lib/db/neostore.propertystore.db.strings.id +0 -0
  24. data/lib/db/neostore.relationshipstore.db +0 -0
  25. data/lib/db/neostore.relationshipstore.db.id +0 -0
  26. data/lib/db/neostore.relationshiptypestore.db +0 -0
  27. data/lib/db/neostore.relationshiptypestore.db.id +0 -0
  28. data/lib/db/neostore.relationshiptypestore.db.names +0 -0
  29. data/lib/db/neostore.relationshiptypestore.db.names.id +0 -0
  30. data/lib/db/nioneo_logical.log.2 +0 -0
  31. data/lib/db/nioneo_logical.log.active +0 -0
  32. data/lib/db/tm_tx_log.1 +0 -0
  33. data/lib/neo4j/config.rb +139 -0
  34. data/lib/neo4j/cypher.rb +156 -0
  35. data/lib/neo4j/neo4j.rb +244 -0
  36. data/lib/neo4j/neo4j.rb~ +214 -0
  37. data/lib/neo4j/node.rb +39 -0
  38. data/lib/neo4j/relationship.rb +61 -0
  39. data/lib/neo4j/transaction.rb +86 -0
  40. data/lib/neo4j/type_converters/type_converters.rb +287 -0
  41. data/lib/neo4j-core/cypher/cypher.rb +867 -0
  42. data/lib/neo4j-core/cypher/result_wrapper.rb +39 -0
  43. data/lib/neo4j-core/database.rb +191 -0
  44. data/lib/neo4j-core/equal/equal.rb +23 -0
  45. data/lib/neo4j-core/event_handler.rb +265 -0
  46. data/lib/neo4j-core/index/class_methods.rb +117 -0
  47. data/lib/neo4j-core/index/index.rb +36 -0
  48. data/lib/neo4j-core/index/index_config.rb +112 -0
  49. data/lib/neo4j-core/index/indexer.rb +243 -0
  50. data/lib/neo4j-core/index/indexer_registry.rb +55 -0
  51. data/lib/neo4j-core/index/lucene_query.rb +264 -0
  52. data/lib/neo4j-core/lazy_map.rb +21 -0
  53. data/lib/neo4j-core/node/class_methods.rb +77 -0
  54. data/lib/neo4j-core/node/node.rb +47 -0
  55. data/lib/neo4j-core/property/property.rb +94 -0
  56. data/lib/neo4j-core/relationship/class_methods.rb +80 -0
  57. data/lib/neo4j-core/relationship/relationship.rb +97 -0
  58. data/lib/neo4j-core/relationship_set.rb +61 -0
  59. data/lib/neo4j-core/rels/rels.rb +147 -0
  60. data/lib/neo4j-core/rels/traverser.rb +99 -0
  61. data/lib/neo4j-core/to_java.rb +51 -0
  62. data/lib/neo4j-core/traversal/evaluator.rb +36 -0
  63. data/lib/neo4j-core/traversal/filter_predicate.rb +30 -0
  64. data/lib/neo4j-core/traversal/prune_evaluator.rb +20 -0
  65. data/lib/neo4j-core/traversal/rel_expander.rb +35 -0
  66. data/lib/neo4j-core/traversal/traversal.rb +130 -0
  67. data/lib/neo4j-core/traversal/traverser.rb +295 -0
  68. data/lib/neo4j-core/version.rb +5 -0
  69. data/lib/neo4j-core.rb +64 -0
  70. data/neo4j-core.gemspec +31 -0
  71. metadata +145 -0
@@ -0,0 +1,112 @@
1
+ module Neo4j
2
+ module Core
3
+ module Index
4
+
5
+ # Responsible for holding the configuration for one index
6
+ # Is used in a DSL to configure the index.
7
+ class IndexConfig
8
+ attr_reader :_trigger_on, :_index_names, :entity_type
9
+
10
+ def initialize(entity_type)
11
+ @entity_type = entity_type
12
+ @index_type = {}
13
+ @numeric_types = []
14
+ @_trigger_on = {}
15
+ @decl_type = {}
16
+ end
17
+
18
+ # Specifies which property and values the index should be triggered on.
19
+ # Used in the Index DSL.
20
+ # @see Neo4j::Core::Index::ClassMethods#node_indexer
21
+ def trigger_on(hash)
22
+ merge_and_to_string(@_trigger_on, hash)
23
+ end
24
+
25
+ # Specifies the types of the properties being indexed so that the proper sort order can be applied.
26
+ # Used in the Index DSL.
27
+ # @see Neo4j::Core::Index::ClassMethods#node_indexer
28
+ def decl_type(prop_and_type_hash)
29
+ merge_and_to_string(@decl_type, prop_and_type_hash)
30
+ end
31
+
32
+ # Specifies an index with configuration
33
+ # Used in the Index DSL.
34
+ # @param [Hash] args the field and its configuration TODO
35
+ # @see Neo4j::Core::Index::ClassMethods#index
36
+ def index(args)
37
+ conf = args.last.kind_of?(Hash) ? args.pop : {}
38
+
39
+ args.uniq.each do |field|
40
+ @index_type[field.to_s] = conf[:type] || :exact
41
+ @numeric_types << field.to_s if conf[:numeric] == true
42
+ end
43
+ end
44
+
45
+ # @return [Class] the specified type of the property or String
46
+ # @see #decl_type
47
+ def decl_type_on(prop)
48
+ @decl_type[prop] || String
49
+ end
50
+
51
+ # @return [true, false] if the props can/should trigger an index operation
52
+ def trigger_on?(props)
53
+ @_trigger_on.each_pair { |k, v| break true if (a = props[k]) && (v.include?(a)) } == true
54
+ end
55
+
56
+ # @return the index name for the lucene index given a type
57
+ def index_name_for_type(type)
58
+ _prefix_index_name + @_index_names[type]
59
+ end
60
+
61
+ # Defines the
62
+ def prefix_index_name(&block)
63
+ @prefix_index_name_block = block
64
+ end
65
+
66
+ def _prefix_index_name
67
+ @prefix_index_name_block.nil? ? "" : @prefix_index_name_block.call
68
+ end
69
+
70
+ def index_names(hash)
71
+ @_index_names = hash
72
+ end
73
+
74
+
75
+ def rm_index_config
76
+ @index_type = {}
77
+ @numeric_types = []
78
+ end
79
+
80
+ def index_type(field)
81
+ @index_type[field.to_s]
82
+ end
83
+
84
+ def has_index_type?(type)
85
+ @index_type.values.include?(type)
86
+ end
87
+
88
+ def fields
89
+ @index_type.keys
90
+ end
91
+
92
+ def index?(field)
93
+ @index_type.include?(field.to_s)
94
+ end
95
+
96
+ def numeric?(field)
97
+ return true if @numeric_types.include?(field)
98
+ # TODO callback to numeric dsl for Neo4j::NodeMixin decl_props check
99
+ end
100
+
101
+ private
102
+
103
+ def merge_and_to_string(existing_hash, new_hash)
104
+ new_hash.each_pair do |k, v|
105
+ existing_hash[k.to_s] ||= Set.new
106
+ existing_hash[k.to_s].merge(v.is_a?(Array)? v : [v])
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,243 @@
1
+ module Neo4j
2
+ module Core
3
+ module Index
4
+
5
+ # This class is delegated from the Neo4j::Core::Index::ClassMethod
6
+ # @see Neo4j::Core::Index::ClassMethods
7
+ class Indexer
8
+ # @return [Neo4j::Core::Index::IndexConfig]
9
+ attr_reader :config
10
+
11
+ def initialize(config)
12
+ @config = config
13
+ @indexes = {} # key = type, value = java neo4j index
14
+ # to enable subclass indexing to work properly, store a list of parent indexers and
15
+ # whenever an operation is performed on this one, perform it on all
16
+ end
17
+
18
+
19
+ def to_s
20
+ "Indexer @#{object_id} index on: [#{@config.fields.map{|f| @config.numeric?(f)? "#{f} (numeric)" : f}.join(', ')}]"
21
+ end
22
+
23
+ # Add an index on a field so that it will be automatically updated by neo4j transactional events.
24
+ #
25
+ # The index method takes an optional configuration hash which allows you to:
26
+ #
27
+ # @example Add an index on an a property
28
+ #
29
+ # class Person
30
+ # include Neo4j::NodeMixin
31
+ # index :name
32
+ # end
33
+ #
34
+ # When the property name is changed/deleted or the node created it will keep the lucene index in sync.
35
+ # You can then perform a lucene query like this: Person.find('name: andreas')
36
+ #
37
+ # @example Add index on other nodes.
38
+ #
39
+ # class Person
40
+ # include Neo4j::NodeMixin
41
+ # has_n(:friends).to(Contact)
42
+ # has_n(:known_by).from(:friends)
43
+ # index :user_id, :via => :known_by
44
+ # end
45
+ #
46
+ # Notice that you *must* specify an incoming relationship with the via key, as shown above.
47
+ # In the example above an index <tt>user_id</tt> will be added to all Person nodes which has a <tt>friends</tt> relationship
48
+ # that person with that user_id. This allows you to do lucene queries on your friends properties.
49
+ #
50
+ # @example Set the type value to index
51
+ #
52
+ # class Person
53
+ # include Neo4j::NodeMixin
54
+ # property :height, :weight, :type => Float
55
+ # index :height, :weight
56
+ # end
57
+ #
58
+ # By default all values will be indexed as Strings.
59
+ # If you want for example to do a numerical range query you must tell Neo4j.rb to index it as a numeric value.
60
+ # You do that with the key <tt>type</tt> on the property.
61
+ #
62
+ # Supported values for <tt>:type</tt> is <tt>String</tt>, <tt>Float</tt>, <tt>Date</tt>, <tt>DateTime</tt> and <tt>Fixnum</tt>
63
+ #
64
+ # === For more information
65
+ #
66
+ # @see Neo4j::Core::Index::LuceneQuery
67
+ # @see #find
68
+ #
69
+ def index(*args)
70
+ @config.index(args)
71
+ end
72
+
73
+ # @return [true,false] if there is an index on the given field.
74
+ def index?(field)
75
+ @config.index?(field)
76
+ end
77
+
78
+ # @return [true,false] if the
79
+ def trigger_on?(props)
80
+ @config.trigger_on?(props)
81
+ end
82
+
83
+ # @return [Symbol] the type of index for the given field (e.g. :exact or :fulltext)
84
+ def index_type(field)
85
+ @config.index_type(field)
86
+ end
87
+
88
+ # @return [true,false] if there is an index of the given type defined.
89
+ def has_index_type?(type)
90
+ @config.has_index_type?(type)
91
+ end
92
+
93
+ # Adds an index on the given entity
94
+ # This is normally not needed since you can instead declare an index which will automatically keep
95
+ # the lucene index in sync.
96
+ # @see #index
97
+ def add_index(entity, field, value)
98
+ return false unless index?(field)
99
+ conv_value = indexed_value_for(field, value)
100
+ index = index_for_field(field.to_s)
101
+ index.add(entity, field, conv_value)
102
+ end
103
+
104
+
105
+ # Removes an index on the given entity
106
+ # This is normally not needed since you can instead declare an index which will automatically keep
107
+ # the lucene index in sync.
108
+ # @see #index
109
+ def rm_index(entity, field, value)
110
+ return false unless index?(field)
111
+ index_for_field(field).remove(entity, field, value)
112
+ end
113
+
114
+ # Performs a Lucene Query.
115
+ #
116
+ # In order to use this you have to declare an index on the fields first, see #index.
117
+ # Notice that you should close the lucene query after the query has been executed.
118
+ # You can do that either by provide an block or calling the Neo4j::Core::Index::LuceneQuery#close
119
+ # method. When performing queries from Ruby on Rails you do not need this since it will be automatically closed
120
+ # (by Rack).
121
+ #
122
+ # @example with a block
123
+ #
124
+ # Person.find('name: kalle') {|query| puts "#{[*query].join(', )"}
125
+ #
126
+ # @example
127
+ #
128
+ # query = Person.find('name: kalle')
129
+ # puts "First item #{query.first}"
130
+ # query.close
131
+ #
132
+ # @return [Neo4j::Core::Index::LuceneQuery] a query object
133
+ def find(query, params = {})
134
+ index = index_for_type(params[:type] || :exact)
135
+ if query.is_a?(Hash) && (query.include?(:conditions) || query.include?(:sort))
136
+ params.merge! query.reject { |k, _| k == :conditions }
137
+ query.delete(:sort)
138
+ query = query.delete(:conditions) if query.include?(:conditions)
139
+ end
140
+ query = (params[:wrapped].nil? || params[:wrapped]) ? LuceneQuery.new(index, @config, query, params) : index.query(query)
141
+
142
+ if block_given?
143
+ begin
144
+ ret = yield query
145
+ ensure
146
+ query.close
147
+ end
148
+ ret
149
+ else
150
+ query
151
+ end
152
+ end
153
+
154
+ # Delete all index configuration. No more automatic indexing will be performed
155
+ def rm_index_config
156
+ @config.rm_index_config
157
+ end
158
+
159
+ # delete the index, if no type is provided clear all types of indexes
160
+ def rm_index_type(type=nil)
161
+ if type
162
+ key = @config.index_name_for_type(type)
163
+ @indexes[key] && @indexes[key].delete
164
+ @indexes[key] = nil
165
+ else
166
+ @indexes.each_value { |index| index.delete }
167
+ @indexes.clear
168
+ end
169
+ end
170
+
171
+ # Called when the neo4j shutdown in order to release references to indexes
172
+ def on_neo4j_shutdown
173
+ @indexes.clear
174
+ end
175
+
176
+ # Called from the event handler when a new node or relationships is about to be committed.
177
+ def update_index_on(node, field, old_val, new_val)
178
+ if index?(field)
179
+ rm_index(node, field, old_val) if old_val
180
+ add_index(node, field, new_val) if new_val
181
+ end
182
+ end
183
+
184
+ # Called from the event handler when deleting a property
185
+ def remove_index_on(node, old_props)
186
+ @config.fields.each { |field| rm_index(node, field, old_props[field]) if old_props[field] }
187
+ end
188
+
189
+ # Creates a wrapped ValueContext for the given value. Checks if it's numeric value in the configuration.
190
+ # @return [Java::OrgNeo4jIndexLucene::ValueContext] a wrapped neo4j lucene value context
191
+ def indexed_value_for(field, value)
192
+ if @config.numeric?(field)
193
+ Java::OrgNeo4jIndexLucene::ValueContext.new(value).indexNumeric
194
+ else
195
+ Java::OrgNeo4jIndexLucene::ValueContext.new(value)
196
+ end
197
+ end
198
+
199
+ # @return [Java::OrgNeo4jGraphdb::Index] for the given field
200
+ def index_for_field(field)
201
+ type = @config.index_type(field)
202
+ index_name = index_name_for_type(type)
203
+ @indexes[index_name] ||= create_index_with(type, index_name)
204
+ end
205
+
206
+ # @return [Java::OrgNeo4jGraphdb::Index] for the given index type
207
+ def index_for_type(type)
208
+ index_name = index_name_for_type(type)
209
+ @indexes[index_name] ||= create_index_with(type, index_name)
210
+ end
211
+
212
+ # @return [String] the name of the index which are stored on the filesystem
213
+ def index_name_for_type(type)
214
+ @config.index_name_for_type(type)
215
+ end
216
+
217
+ # @return [Hash] the lucene config for the given index type
218
+ def lucene_config(type)
219
+ conf = Neo4j::Config[:lucene][type.to_s]
220
+ raise "unknown lucene type #{type}" unless conf
221
+ conf
222
+ end
223
+
224
+ # Creates a new lucene index using the lucene configuration for the given index_name
225
+ #
226
+ # @param [:node, :relationship] type relationship or node index
227
+ # @param [String] index_name the (file) name of the index
228
+ # @return [Java::OrgNeo4jGraphdb::Index] for the given index type
229
+ def create_index_with(type, index_name)
230
+ db = Neo4j.started_db
231
+ index_config = lucene_config(type)
232
+ if config.entity_type == :node
233
+ db.lucene.for_nodes(index_name, index_config)
234
+ else
235
+ db.lucene.for_relationships(index_name, index_config)
236
+ end
237
+ end
238
+
239
+
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,55 @@
1
+ module Neo4j
2
+ module Core
3
+ module Index
4
+ class IndexerRegistry
5
+ def initialize
6
+ @indexers = []
7
+ end
8
+
9
+ def delete_all_indexes
10
+ @indexers.each { |i| i.rm_index_type }
11
+ end
12
+
13
+ def register(indexer)
14
+ @indexers << indexer
15
+ indexer
16
+ end
17
+
18
+ def indexers_for(props)
19
+ Enumerator.new(self, :each_indexer, props)
20
+ end
21
+
22
+ def each_indexer(props)
23
+ @indexers.each { |i| yield i if i.trigger_on?(props) }
24
+ end
25
+
26
+ def on_node_deleted(node, old_props, *)
27
+ each_indexer(old_props) { |indexer| indexer.remove_index_on(node, old_props) }
28
+ end
29
+
30
+ def on_property_changed(node, field, old_val, new_val)
31
+ each_indexer(node) { |indexer| indexer.update_index_on(node, field, old_val, new_val) }
32
+ end
33
+
34
+ def on_relationship_deleted(relationship, old_props, *)
35
+ each_indexer(old_props) { |indexer| indexer.remove_index_on(relationship, old_props) }
36
+ end
37
+
38
+ def on_rel_property_changed(rel, field, old_val, new_val)
39
+ each_indexer(rel) { |indexer| indexer.update_index_on(rel, field, old_val, new_val) }
40
+ end
41
+
42
+ def on_neo4j_shutdown(*)
43
+ @indexers.each { |indexer| indexer.on_neo4j_shutdown }
44
+ end
45
+
46
+ class << self
47
+ def instance
48
+ @@instance ||= IndexerRegistry.new
49
+ end
50
+ end
51
+ end
52
+ Neo4j.unstarted_db.event_handler.add(IndexerRegistry.instance) unless Neo4j.read_only?
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,264 @@
1
+ module Neo4j
2
+ module Core
3
+
4
+ module Index
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
+ # @example By Hash
11
+ #
12
+ # Person.find(:name => 'foo', :age => 3)
13
+ #
14
+ # @example By Range
15
+ #
16
+ # Person.find(:age).between(15,35)
17
+ #
18
+ # @example By Lucene Query Syntax
19
+ #
20
+ # Car.find('wheels:"4" AND colour: "blue")
21
+ #
22
+ # For more information about the syntax see http://lucene.apache.org/java/3_0_2/queryparsersyntax.html
23
+ #
24
+ # @example: By Compound Queries
25
+ #
26
+ # Vehicle.find(:weight).between(5.0, 100000.0).and(:name).between('a', 'd')
27
+ #
28
+ # You can combine several queries by <tt>AND</tt>ing those together.
29
+ #
30
+ # @see Neo4j::Core::Index::Indexer#index
31
+ # @see Neo4j::Core::Index::Indexer#find - which returns an LuceneQuery
32
+ #
33
+ class LuceneQuery
34
+ include Enumerable
35
+ attr_accessor :left_and_query, :left_or_query, :right_not_query, :query, :order
36
+
37
+ def initialize(index, index_config, query, params={})
38
+ @index = index
39
+ @query = query
40
+ @index_config = index_config
41
+ @params = params
42
+
43
+ if params.include?(:sort)
44
+ @order = {}
45
+ params[:sort].each_pair { |k, v| @order[k] = (v == :desc) }
46
+ end
47
+ end
48
+
49
+ # Implements the Ruby +Enumerable+ interface
50
+ def each
51
+ hits.each { |x| yield x.wrapper }
52
+ end
53
+
54
+ # Close hits
55
+ #
56
+ # 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.
57
+ # It's necessary to call it so that underlying indexes can dispose of allocated resources for this search result.
58
+ # You can however skip to call this method if you loop through the whole result, then close() will be called automatically.
59
+ # Even if you loop through the entire result and then call this method it will silently ignore any consequtive call (for convenience).
60
+ #
61
+ # This must be done according to the Neo4j Java Documentation:
62
+ def close
63
+ @hits.close if @hits
64
+ @hits = nil
65
+ end
66
+
67
+ # @return [true, false] True if there is no search hits.
68
+ def empty?
69
+ hits.size == 0
70
+ end
71
+
72
+ # Does simply loop all search items till the n'th is found.
73
+ # @return[Neo4j::Node, Neo4j::Relationship] the n'th search item
74
+ def [](index)
75
+ i = 0
76
+ each { |x| return x if i == index; i += 1 }
77
+ nil # out of index
78
+ end
79
+
80
+ # @return[Fixnum] the number of search hits
81
+ def size
82
+ hits.size
83
+ end
84
+
85
+ # Performs a range query
86
+ # Notice that if you don't specify a type when declaring a property a String range query will be performed.
87
+ #
88
+ def between(lower, upper, lower_incusive=false, upper_inclusive=false)
89
+ raise "Expected a symbol. Syntax for range queries example: index(:weight).between(a,b)" unless Symbol === @query
90
+ # raise "Can't only do range queries on Neo4j::NodeMixin, Neo4j::Model, Neo4j::RelationshipMixin" unless @decl_props
91
+ # check that we perform a range query on the same values as we have declared with the property :key, :type => ...
92
+ type = @index_config.decl_type_on(@query)
93
+ raise "find(#{@query}).between(#{lower}, #{upper}): #{lower} not a #{type}" if type && !type === lower.class
94
+ raise "find(#{@query}).between(#{lower}, #{upper}): #{upper} not a #{type}" if type && !type === upper.class
95
+
96
+ # Make it possible to convert those values
97
+ @query = range_query(@query, lower, upper, lower_incusive, upper_inclusive)
98
+ self
99
+ end
100
+
101
+ def range_query(field, lower, upper, lower_incusive, upper_inclusive)
102
+ lower = TypeConverters.convert(lower)
103
+ upper = TypeConverters.convert(upper)
104
+
105
+ case lower
106
+ when Fixnum
107
+ Java::OrgApacheLuceneSearch::NumericRangeQuery.new_long_range(field.to_s, lower, upper, lower_incusive, upper_inclusive)
108
+ when Float
109
+ Java::OrgApacheLuceneSearch::NumericRangeQuery.new_double_range(field.to_s, lower, upper, lower_incusive, upper_inclusive)
110
+ else
111
+ Java::OrgApacheLuceneSearch::TermRangeQuery.new(field.to_s, lower, upper, lower_incusive, upper_inclusive)
112
+ end
113
+ end
114
+
115
+
116
+ # Create a compound lucene query.
117
+ #
118
+ # @param [String] query2 the query that should be AND together
119
+ # @return [Neo4j::Core::Index::LuceneQuery] a new query object
120
+ #
121
+ # @example
122
+ #
123
+ # Person.find(:name=>'kalle').and(:age => 3)
124
+ #
125
+ def and(query2)
126
+ LuceneQuery.new(@index, @index_config, query2).tap { |new_query| new_query.left_and_query = self }
127
+ end
128
+
129
+ # Create an OR lucene query.
130
+ #
131
+ # @param [String] query2 the query that should be OR together
132
+ # @return [Neo4j::Core::Index::LuceneQuery] a new query object
133
+ #
134
+ # @example
135
+ #
136
+ # Person.find(:name=>'kalle').or(:age => 3)
137
+ #
138
+ def or(query2)
139
+ LuceneQuery.new(@index, @index_config, query2).tap { |new_query| new_query.left_or_query = self }
140
+ end
141
+
142
+ # Create a NOT lucene query.
143
+ #
144
+ # @param [String] query2 the query that should exclude matching results
145
+ # @return [Neo4j::Core::Index::LuceneQuery] a new query object
146
+ #
147
+ # @example
148
+ #
149
+ # Person.find(:age => 3).not(:name=>'kalle')
150
+ #
151
+ def not(query2)
152
+ LuceneQuery.new(@index, @index_config, query2).tap { |new_query| new_query.right_not_query = self }
153
+ end
154
+
155
+
156
+ # Sort descending the given fields.
157
+ # @param [Symbol] fields it should sort
158
+ def desc(*fields)
159
+ @order = fields.inject(@order || {}) { |memo, field| memo[field] = true; memo }
160
+ self
161
+ end
162
+
163
+ # Sort ascending the given fields.
164
+ # @param [Symbol] fields it should sort
165
+ def asc(*fields)
166
+ @order = fields.inject(@order || {}) { |memo, field| memo[field] = false; memo }
167
+ self
168
+ end
169
+
170
+ protected
171
+
172
+ def build_and_query(query)
173
+ build_composite_query(@left_and_query.build_query, query, Java::OrgApacheLuceneSearch::BooleanClause::Occur::MUST)
174
+ end
175
+
176
+ def build_or_query(query)
177
+ build_composite_query(@left_or_query.build_query, query, Java::OrgApacheLuceneSearch::BooleanClause::Occur::SHOULD)
178
+ end
179
+
180
+ def build_not_query(query)
181
+ right_query = @right_not_query.build_query
182
+ composite_query = Java::OrgApacheLuceneSearch::BooleanQuery.new
183
+ composite_query.add(query, Java::OrgApacheLuceneSearch::BooleanClause::Occur::MUST_NOT)
184
+ composite_query.add(right_query, Java::OrgApacheLuceneSearch::BooleanClause::Occur::MUST)
185
+ composite_query
186
+ end
187
+
188
+ def build_composite_query(left_query, right_query, operator) #:nodoc:
189
+ composite_query = Java::OrgApacheLuceneSearch::BooleanQuery.new
190
+ version = Java::OrgApacheLuceneUtil::Version::LUCENE_35
191
+ analyzer = org.apache.lucene.analysis.standard::StandardAnalyzer.new(version)
192
+ parser = Java::org.apache.lucene.queryParser.QueryParser.new(version,'name', analyzer )
193
+
194
+ left_query = parser.parse(left_query) if left_query.is_a?(String)
195
+ right_query = parser.parse(right_query) if right_query.is_a?(String)
196
+
197
+ composite_query.add(left_query, operator)
198
+ composite_query.add(right_query, operator)
199
+ composite_query
200
+ end
201
+
202
+ def build_sort_query(query) #:nodoc:
203
+ java_sort_fields = @order.keys.inject([]) do |memo, field|
204
+ decl_type = @index_config.decl_type_on(field)
205
+ type = case
206
+ when Float == decl_type
207
+ Java::OrgApacheLuceneSearch::SortField::DOUBLE
208
+ when Fixnum == decl_type || DateTime == decl_type || Date == decl_type || Time == decl_type
209
+ Java::OrgApacheLuceneSearch::SortField::LONG
210
+ else
211
+ Java::OrgApacheLuceneSearch::SortField::STRING
212
+ end
213
+ memo << Java::OrgApacheLuceneSearch::SortField.new(field.to_s, type, @order[field])
214
+ end
215
+ sort = Java::OrgApacheLuceneSearch::Sort.new(*java_sort_fields)
216
+ Java::OrgNeo4jIndexLucene::QueryContext.new(query).sort(sort)
217
+ end
218
+
219
+ def build_hash_query(query) #:nodoc:
220
+ and_query = Java::OrgApacheLuceneSearch::BooleanQuery.new
221
+
222
+ query.each_pair do |key, value|
223
+ type = @index_config && @index_config[key.to_sym] && @index_config[key.to_sym][:type]
224
+ if !type.nil? && type != String
225
+ if Range === value
226
+ and_query.add(range_query(key, value.first, value.last, true, !value.exclude_end?), Java::OrgApacheLuceneIndex::BooleanClause::Occur::MUST)
227
+ else
228
+ and_query.add(range_query(key, value, value, true, true), Java::OrgApacheLuceneIndex::BooleanClause::Occur::MUST)
229
+ end
230
+ else
231
+ conv_value = type ? TypeConverters.convert(value) : value.to_s
232
+ term = Java::OrgApacheLuceneIndex::Term.new(key.to_s, conv_value)
233
+ term_query = Java::OrgApacheLuceneIndex::TermQuery.new(term)
234
+ and_query.add(term_query, Java::OrgApacheLuceneIndex::BooleanClause::Occur::MUST)
235
+ end
236
+ end
237
+ and_query
238
+ end
239
+
240
+ def build_query #:nodoc:
241
+ query = @query
242
+ query = build_hash_query(query) if Hash === query
243
+ query = build_and_query(query) if @left_and_query
244
+ query = build_or_query(query) if @left_or_query
245
+ query = build_not_query(query) if @right_not_query
246
+ query = build_sort_query(query) if @order
247
+ query
248
+ end
249
+
250
+ def perform_query #:nodoc:
251
+ @index.query(build_query)
252
+ end
253
+
254
+
255
+ def hits #:nodoc:
256
+ close
257
+ @hits = perform_query
258
+ end
259
+
260
+
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,21 @@
1
+ module Neo4j
2
+ module Core
3
+ # A simple implementation of lazy map
4
+ # @notice since we must support ruby 1.8.7 we can't use the fancy Enumerator block constructor for this.
5
+ # @private
6
+ class LazyMap
7
+ include Enumerable
8
+
9
+ def initialize(stuff, &map_block)
10
+ @stuff = stuff
11
+ @map_block = map_block
12
+ end
13
+
14
+ def each
15
+ @stuff.each { |x| yield @map_block.call(x) }
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end