neo4j-core 0.0.1-java
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +27 -0
- data/README.rdoc +27 -0
- data/config/neo4j/config.yml +102 -0
- data/lib/db/active_tx_log +1 -0
- data/lib/db/index/lucene-store.db +0 -0
- data/lib/db/index/lucene.log.1 +0 -0
- data/lib/db/index/lucene.log.active +0 -0
- data/lib/db/lock +0 -0
- data/lib/db/messages.log +530 -0
- data/lib/db/neostore +0 -0
- data/lib/db/neostore.id +0 -0
- data/lib/db/neostore.nodestore.db +0 -0
- data/lib/db/neostore.nodestore.db.id +0 -0
- data/lib/db/neostore.propertystore.db +0 -0
- data/lib/db/neostore.propertystore.db.arrays +0 -0
- data/lib/db/neostore.propertystore.db.arrays.id +0 -0
- data/lib/db/neostore.propertystore.db.id +0 -0
- data/lib/db/neostore.propertystore.db.index +0 -0
- data/lib/db/neostore.propertystore.db.index.id +0 -0
- data/lib/db/neostore.propertystore.db.index.keys +0 -0
- data/lib/db/neostore.propertystore.db.index.keys.id +0 -0
- data/lib/db/neostore.propertystore.db.strings +0 -0
- data/lib/db/neostore.propertystore.db.strings.id +0 -0
- data/lib/db/neostore.relationshipstore.db +0 -0
- data/lib/db/neostore.relationshipstore.db.id +0 -0
- data/lib/db/neostore.relationshiptypestore.db +0 -0
- data/lib/db/neostore.relationshiptypestore.db.id +0 -0
- data/lib/db/neostore.relationshiptypestore.db.names +0 -0
- data/lib/db/neostore.relationshiptypestore.db.names.id +0 -0
- data/lib/db/nioneo_logical.log.2 +0 -0
- data/lib/db/nioneo_logical.log.active +0 -0
- data/lib/db/tm_tx_log.1 +0 -0
- data/lib/neo4j/config.rb +139 -0
- data/lib/neo4j/cypher.rb +156 -0
- data/lib/neo4j/neo4j.rb +244 -0
- data/lib/neo4j/neo4j.rb~ +214 -0
- data/lib/neo4j/node.rb +39 -0
- data/lib/neo4j/relationship.rb +61 -0
- data/lib/neo4j/transaction.rb +86 -0
- data/lib/neo4j/type_converters/type_converters.rb +287 -0
- data/lib/neo4j-core/cypher/cypher.rb +867 -0
- data/lib/neo4j-core/cypher/result_wrapper.rb +39 -0
- data/lib/neo4j-core/database.rb +191 -0
- data/lib/neo4j-core/equal/equal.rb +23 -0
- data/lib/neo4j-core/event_handler.rb +265 -0
- data/lib/neo4j-core/index/class_methods.rb +117 -0
- data/lib/neo4j-core/index/index.rb +36 -0
- data/lib/neo4j-core/index/index_config.rb +112 -0
- data/lib/neo4j-core/index/indexer.rb +243 -0
- data/lib/neo4j-core/index/indexer_registry.rb +55 -0
- data/lib/neo4j-core/index/lucene_query.rb +264 -0
- data/lib/neo4j-core/lazy_map.rb +21 -0
- data/lib/neo4j-core/node/class_methods.rb +77 -0
- data/lib/neo4j-core/node/node.rb +47 -0
- data/lib/neo4j-core/property/property.rb +94 -0
- data/lib/neo4j-core/relationship/class_methods.rb +80 -0
- data/lib/neo4j-core/relationship/relationship.rb +97 -0
- data/lib/neo4j-core/relationship_set.rb +61 -0
- data/lib/neo4j-core/rels/rels.rb +147 -0
- data/lib/neo4j-core/rels/traverser.rb +99 -0
- data/lib/neo4j-core/to_java.rb +51 -0
- data/lib/neo4j-core/traversal/evaluator.rb +36 -0
- data/lib/neo4j-core/traversal/filter_predicate.rb +30 -0
- data/lib/neo4j-core/traversal/prune_evaluator.rb +20 -0
- data/lib/neo4j-core/traversal/rel_expander.rb +35 -0
- data/lib/neo4j-core/traversal/traversal.rb +130 -0
- data/lib/neo4j-core/traversal/traverser.rb +295 -0
- data/lib/neo4j-core/version.rb +5 -0
- data/lib/neo4j-core.rb +64 -0
- data/neo4j-core.gemspec +31 -0
- 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
|