neo4j-core 0.0.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|