thinking-sphinx 1.4.6 → 1.4.7
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/README.textile +6 -1
- data/features/searching_by_model.feature +24 -30
- data/features/thinking_sphinx/db/.gitignore +1 -0
- data/features/thinking_sphinx/db/fixtures/post_keywords.txt +1 -0
- data/lib/cucumber/thinking_sphinx/internal_world.rb +26 -26
- data/lib/thinking_sphinx.rb +17 -26
- data/lib/thinking_sphinx/active_record.rb +69 -74
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +11 -10
- data/lib/thinking_sphinx/active_record/has_many_association.rb +2 -1
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +11 -11
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +34 -20
- data/lib/thinking_sphinx/association.rb +12 -7
- data/lib/thinking_sphinx/attribute.rb +64 -61
- data/lib/thinking_sphinx/configuration.rb +32 -36
- data/lib/thinking_sphinx/context.rb +3 -2
- data/lib/thinking_sphinx/deploy/capistrano.rb +7 -9
- data/lib/thinking_sphinx/search.rb +201 -178
- data/lib/thinking_sphinx/source/sql.rb +1 -1
- data/lib/thinking_sphinx/tasks.rb +21 -19
- data/lib/thinking_sphinx/version.rb +3 -0
- data/spec/fixtures/data.sql +32 -0
- data/spec/fixtures/database.yml.default +3 -0
- data/spec/fixtures/models.rb +161 -0
- data/spec/fixtures/structure.sql +146 -0
- data/spec/spec_helper.rb +57 -0
- data/spec/sphinx_helper.rb +61 -0
- data/spec/thinking_sphinx/active_record/delta_spec.rb +24 -24
- data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +22 -0
- data/spec/thinking_sphinx/active_record/scopes_spec.rb +25 -25
- data/spec/thinking_sphinx/active_record_spec.rb +110 -109
- data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +38 -38
- data/spec/thinking_sphinx/association_spec.rb +20 -2
- data/spec/thinking_sphinx/context_spec.rb +61 -64
- data/spec/thinking_sphinx/search_spec.rb +7 -0
- data/spec/thinking_sphinx_spec.rb +47 -46
- metadata +50 -98
- data/VERSION +0 -1
- data/tasks/distribution.rb +0 -34
- data/tasks/testing.rb +0 -80
@@ -15,6 +15,7 @@ module ThinkingSphinx
|
|
15
15
|
# address:: 127.0.0.1
|
16
16
|
# port:: 9312
|
17
17
|
# allow star:: false
|
18
|
+
# stop timeout:: 5
|
18
19
|
# min prefix length:: 1
|
19
20
|
# min infix length:: 1
|
20
21
|
# mem limit:: 64M
|
@@ -49,29 +50,25 @@ module ThinkingSphinx
|
|
49
50
|
class Configuration
|
50
51
|
include Singleton
|
51
52
|
|
52
|
-
SourceOptions =
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
attr_accessor :searchd_file_path, :allow_star, :database_yml_file,
|
69
|
-
:app_root, :model_directories, :delayed_job_priority, :indexed_models
|
53
|
+
SourceOptions = Riddle::Configuration::SQLSource.settings.map { |setting|
|
54
|
+
setting.to_s
|
55
|
+
} - %w( type sql_query_pre sql_query sql_joined_field sql_file_field
|
56
|
+
sql_query_range sql_attr_uint sql_attr_bool sql_attr_bigint sql_query_info
|
57
|
+
sql_attr_timestamp sql_attr_str2ordinal sql_attr_float sql_attr_multi
|
58
|
+
sql_attr_string sql_attr_str2wordcount sql_column_buffers sql_field_string
|
59
|
+
sql_field_str2wordcount )
|
60
|
+
IndexOptions = Riddle::Configuration::Index.settings.map { |setting|
|
61
|
+
setting.to_s
|
62
|
+
} - %w( source prefix_fields infix_fields )
|
63
|
+
CustomOptions = %w( disable_range use_64_bit )
|
64
|
+
|
65
|
+
attr_accessor :searchd_file_path, :allow_star, :app_root,
|
66
|
+
:model_directories, :delayed_job_priority, :indexed_models, :use_64_bit,
|
67
|
+
:touched_reindex_file, :stop_timeout, :version
|
70
68
|
|
71
69
|
attr_accessor :source_options, :index_options
|
72
|
-
attr_accessor :version
|
73
70
|
|
74
|
-
attr_reader :
|
71
|
+
attr_reader :configuration, :controller
|
75
72
|
|
76
73
|
@@environment = nil
|
77
74
|
|
@@ -107,9 +104,9 @@ module ThinkingSphinx
|
|
107
104
|
|
108
105
|
self.address = "127.0.0.1"
|
109
106
|
self.port = 9312
|
110
|
-
self.database_yml_file = "#{self.app_root}/config/database.yml"
|
111
107
|
self.searchd_file_path = "#{self.app_root}/db/sphinx/#{environment}"
|
112
108
|
self.allow_star = false
|
109
|
+
self.stop_timeout = 5
|
113
110
|
self.model_directories = ["#{app_root}/app/models/"] +
|
114
111
|
Dir.glob("#{app_root}/vendor/plugins/*/app/models/")
|
115
112
|
self.delayed_job_priority = 0
|
@@ -128,21 +125,15 @@ module ThinkingSphinx
|
|
128
125
|
end
|
129
126
|
|
130
127
|
def self.environment
|
131
|
-
if
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
else
|
140
|
-
ENV['RAILS_ENV'] || 'development'
|
141
|
-
end
|
142
|
-
end
|
128
|
+
@@environment ||= if defined?(Merb)
|
129
|
+
Merb.environment
|
130
|
+
elsif defined?(RAILS_ENV)
|
131
|
+
RAILS_ENV
|
132
|
+
elsif defined?(Sinatra)
|
133
|
+
Sinatra::Application.environment.to_s
|
134
|
+
else
|
135
|
+
ENV['RAILS_ENV'] || 'development'
|
143
136
|
end
|
144
|
-
|
145
|
-
@@environment
|
146
137
|
end
|
147
138
|
|
148
139
|
def self.reset_environment
|
@@ -276,7 +267,12 @@ module ThinkingSphinx
|
|
276
267
|
end
|
277
268
|
end
|
278
269
|
end
|
279
|
-
|
270
|
+
|
271
|
+
def touch_reindex_file(output)
|
272
|
+
return FileUtils.touch(@touched_reindex_file) if @touched_reindex_file and output =~ /succesfully sent SIGHUP to searchd/
|
273
|
+
false
|
274
|
+
end
|
275
|
+
|
280
276
|
private
|
281
277
|
|
282
278
|
# Parse the config/sphinx.yml file - if it exists - then use the attribute
|
@@ -55,12 +55,13 @@ class ThinkingSphinx::Context
|
|
55
55
|
model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
|
56
56
|
|
57
57
|
next if model_name.nil?
|
58
|
+
camelized_model = model_name.camelize
|
58
59
|
next if ::ActiveRecord::Base.send(:subclasses).detect { |model|
|
59
|
-
model.name ==
|
60
|
+
model.name == camelized_model
|
60
61
|
}
|
61
62
|
|
62
63
|
begin
|
63
|
-
|
64
|
+
camelized_model.constantize
|
64
65
|
rescue LoadError
|
65
66
|
model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
|
66
67
|
rescue NameError
|
@@ -61,27 +61,25 @@ DESC
|
|
61
61
|
|
62
62
|
desc "Start the Sphinx daemon"
|
63
63
|
task :start do
|
64
|
-
configure
|
65
|
-
rake "thinking_sphinx:start"
|
64
|
+
rake "thinking_sphinx:configure thinking_sphinx:start"
|
66
65
|
end
|
67
66
|
|
68
67
|
desc "Stop the Sphinx daemon"
|
69
68
|
task :stop do
|
70
|
-
configure
|
71
|
-
rake "thinking_sphinx:stop"
|
69
|
+
rake "thinking_sphinx:configure thinking_sphinx:stop"
|
72
70
|
end
|
73
71
|
|
74
72
|
desc "Stop and then start the Sphinx daemon"
|
75
73
|
task :restart do
|
76
|
-
stop
|
77
|
-
|
74
|
+
rake "thinking_sphinx:configure thinking_sphinx:stop \
|
75
|
+
thinking_sphinx:start"
|
78
76
|
end
|
79
77
|
|
80
78
|
desc "Stop, re-index and then start the Sphinx daemon"
|
81
79
|
task :rebuild do
|
82
|
-
stop
|
83
|
-
|
84
|
-
|
80
|
+
rake "thinking_sphinx:configure thinking_sphinx:stop \
|
81
|
+
thinking_sphinx:reindex \
|
82
|
+
thinking_sphinx:start"
|
85
83
|
end
|
86
84
|
|
87
85
|
desc "Add the shared folder for sphinx files"
|
@@ -6,7 +6,7 @@ module ThinkingSphinx
|
|
6
6
|
# Most times, you will just want a specific model's results - to search and
|
7
7
|
# search_for_ids methods will do the job in exactly the same manner when
|
8
8
|
# called from a model.
|
9
|
-
#
|
9
|
+
#
|
10
10
|
class Search < Array
|
11
11
|
CoreMethods = %w( == class class_eval extend frozen? id instance_eval
|
12
12
|
instance_of? instance_values instance_variable_defined?
|
@@ -15,145 +15,145 @@ module ThinkingSphinx
|
|
15
15
|
respond_to_missing? send should type )
|
16
16
|
SafeMethods = %w( partition private_methods protected_methods
|
17
17
|
public_methods send class )
|
18
|
-
|
18
|
+
|
19
19
|
instance_methods.select { |method|
|
20
20
|
method.to_s[/^__/].nil? && !CoreMethods.include?(method.to_s)
|
21
21
|
}.each { |method|
|
22
22
|
undef_method method
|
23
23
|
}
|
24
|
-
|
24
|
+
|
25
25
|
HashOptions = [:conditions, :with, :without, :with_all, :without_any]
|
26
26
|
ArrayOptions = [:classes, :without_ids]
|
27
|
-
|
27
|
+
|
28
28
|
attr_reader :args, :options
|
29
|
-
|
29
|
+
|
30
30
|
# Deprecated. Use ThinkingSphinx.search
|
31
31
|
def self.search(*args)
|
32
32
|
warn 'ThinkingSphinx::Search.search is deprecated. Please use ThinkingSphinx.search instead.'
|
33
33
|
ThinkingSphinx.search(*args)
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# Deprecated. Use ThinkingSphinx.search_for_ids
|
37
37
|
def self.search_for_ids(*args)
|
38
38
|
warn 'ThinkingSphinx::Search.search_for_ids is deprecated. Please use ThinkingSphinx.search_for_ids instead.'
|
39
39
|
ThinkingSphinx.search_for_ids(*args)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
# Deprecated. Use ThinkingSphinx.search_for_ids
|
43
43
|
def self.search_for_id(*args)
|
44
44
|
warn 'ThinkingSphinx::Search.search_for_id is deprecated. Please use ThinkingSphinx.search_for_id instead.'
|
45
45
|
ThinkingSphinx.search_for_id(*args)
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
# Deprecated. Use ThinkingSphinx.count
|
49
49
|
def self.count(*args)
|
50
50
|
warn 'ThinkingSphinx::Search.count is deprecated. Please use ThinkingSphinx.count instead.'
|
51
51
|
ThinkingSphinx.count(*args)
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
# Deprecated. Use ThinkingSphinx.facets
|
55
55
|
def self.facets(*args)
|
56
56
|
warn 'ThinkingSphinx::Search.facets is deprecated. Please use ThinkingSphinx.facets instead.'
|
57
57
|
ThinkingSphinx.facets(*args)
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
60
|
def self.bundle_searches(enum = nil)
|
61
61
|
bundle = ThinkingSphinx::BundledSearch.new
|
62
|
-
|
62
|
+
|
63
63
|
if enum.nil?
|
64
64
|
yield bundle
|
65
65
|
else
|
66
66
|
enum.each { |item| yield bundle, item }
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
bundle.searches
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
def self.matching_fields(fields, bitmask)
|
73
73
|
matches = []
|
74
74
|
bitstring = bitmask.to_s(2).rjust(32, '0').reverse
|
75
|
-
|
75
|
+
|
76
76
|
fields.each_with_index do |field, index|
|
77
77
|
matches << field if bitstring[index, 1] == '1'
|
78
78
|
end
|
79
79
|
matches
|
80
80
|
end
|
81
|
-
|
81
|
+
|
82
82
|
def initialize(*args)
|
83
83
|
ThinkingSphinx.context.define_indexes
|
84
|
-
|
84
|
+
|
85
85
|
@array = []
|
86
86
|
@options = args.extract_options!
|
87
87
|
@args = args
|
88
|
-
|
88
|
+
|
89
89
|
add_default_scope unless options[:ignore_default]
|
90
|
-
|
90
|
+
|
91
91
|
populate if @options[:populate]
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
def to_a
|
95
95
|
populate
|
96
96
|
@array
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
def freeze
|
100
100
|
populate
|
101
101
|
@array.freeze
|
102
102
|
self
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
# Indication of whether the request has been made to Sphinx for the search
|
106
106
|
# query.
|
107
|
-
#
|
107
|
+
#
|
108
108
|
# @return [Boolean] true if the results have been requested.
|
109
|
-
#
|
109
|
+
#
|
110
110
|
def populated?
|
111
111
|
!!@populated
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
# Indication of whether the request resulted in an error from Sphinx.
|
115
|
-
#
|
115
|
+
#
|
116
116
|
# @return [Boolean] true if Sphinx reports query error
|
117
|
-
#
|
117
|
+
#
|
118
118
|
def error?
|
119
119
|
!!error
|
120
120
|
end
|
121
|
-
|
121
|
+
|
122
122
|
# The Sphinx-reported error, if any.
|
123
|
-
#
|
123
|
+
#
|
124
124
|
# @return [String, nil]
|
125
|
-
#
|
125
|
+
#
|
126
126
|
def error
|
127
127
|
populate
|
128
128
|
@results[:error]
|
129
129
|
end
|
130
|
-
|
130
|
+
|
131
131
|
# Indication of whether the request resulted in a warning from Sphinx.
|
132
|
-
#
|
132
|
+
#
|
133
133
|
# @return [Boolean] true if Sphinx reports query warning
|
134
|
-
#
|
134
|
+
#
|
135
135
|
def warning?
|
136
136
|
!!warning
|
137
137
|
end
|
138
|
-
|
138
|
+
|
139
139
|
# The Sphinx-reported warning, if any.
|
140
|
-
#
|
140
|
+
#
|
141
141
|
# @return [String, nil]
|
142
|
-
#
|
142
|
+
#
|
143
143
|
def warning
|
144
144
|
populate
|
145
145
|
@results[:warning]
|
146
146
|
end
|
147
|
-
|
147
|
+
|
148
148
|
# The query result hash from Riddle.
|
149
|
-
#
|
149
|
+
#
|
150
150
|
# @return [Hash] Raw Sphinx results
|
151
|
-
#
|
151
|
+
#
|
152
152
|
def results
|
153
153
|
populate
|
154
154
|
@results
|
155
155
|
end
|
156
|
-
|
156
|
+
|
157
157
|
def method_missing(method, *args, &block)
|
158
158
|
if is_scope?(method)
|
159
159
|
add_scope(method, *args, &block)
|
@@ -166,62 +166,70 @@ module ThinkingSphinx
|
|
166
166
|
elsif !SafeMethods.include?(method.to_s)
|
167
167
|
populate
|
168
168
|
end
|
169
|
-
|
169
|
+
|
170
170
|
if method.to_s[/^each_with_.*/] && !@array.respond_to?(method)
|
171
171
|
each_with_attribute method.to_s.gsub(/^each_with_/, ''), &block
|
172
172
|
else
|
173
173
|
@array.send(method, *args, &block)
|
174
174
|
end
|
175
175
|
end
|
176
|
-
|
176
|
+
|
177
177
|
# Returns true if the Search object or the underlying Array object respond
|
178
178
|
# to the requested method.
|
179
|
-
#
|
179
|
+
#
|
180
180
|
# @param [Symbol] method The method name
|
181
181
|
# @return [Boolean] true if either Search or Array responds to the method.
|
182
|
-
#
|
182
|
+
#
|
183
183
|
def respond_to?(method, include_private = false)
|
184
184
|
super || @array.respond_to?(method, include_private)
|
185
185
|
end
|
186
|
-
|
186
|
+
|
187
187
|
# The current page number of the result set. Defaults to 1 if no page was
|
188
188
|
# explicitly requested.
|
189
|
-
#
|
189
|
+
#
|
190
190
|
# @return [Integer]
|
191
|
-
#
|
191
|
+
#
|
192
192
|
def current_page
|
193
193
|
@options[:page].blank? ? 1 : @options[:page].to_i
|
194
194
|
end
|
195
|
-
|
195
|
+
|
196
|
+
def first_page?
|
197
|
+
current_page == 1
|
198
|
+
end
|
199
|
+
|
196
200
|
# Kaminari support
|
197
201
|
def page(page_number)
|
198
202
|
@options[:page] = page_number
|
199
203
|
self
|
200
204
|
end
|
201
|
-
|
205
|
+
|
202
206
|
# The next page number of the result set. If there are no more pages
|
203
207
|
# available, nil is returned.
|
204
|
-
#
|
208
|
+
#
|
205
209
|
# @return [Integer, nil]
|
206
|
-
#
|
210
|
+
#
|
207
211
|
def next_page
|
208
212
|
current_page >= total_pages ? nil : current_page + 1
|
209
213
|
end
|
210
|
-
|
214
|
+
|
215
|
+
def next_page?
|
216
|
+
!next_page.nil?
|
217
|
+
end
|
218
|
+
|
211
219
|
# The previous page number of the result set. If this is the first page,
|
212
220
|
# then nil is returned.
|
213
|
-
#
|
221
|
+
#
|
214
222
|
# @return [Integer, nil]
|
215
|
-
#
|
223
|
+
#
|
216
224
|
def previous_page
|
217
225
|
current_page == 1 ? nil : current_page - 1
|
218
226
|
end
|
219
|
-
|
227
|
+
|
220
228
|
# The amount of records per set of paged results. Defaults to 20 unless a
|
221
229
|
# specific page size is requested.
|
222
|
-
#
|
230
|
+
#
|
223
231
|
# @return [Integer]
|
224
|
-
#
|
232
|
+
#
|
225
233
|
def per_page
|
226
234
|
@options[:limit] ||= @options[:per_page]
|
227
235
|
@options[:limit] ||= 20
|
@@ -229,29 +237,29 @@ module ThinkingSphinx
|
|
229
237
|
end
|
230
238
|
# Kaminari support
|
231
239
|
alias_method :limit_value, :per_page
|
232
|
-
|
240
|
+
|
233
241
|
# Kaminari support
|
234
242
|
def per(limit)
|
235
243
|
@options[:limit] = limit
|
236
244
|
self
|
237
245
|
end
|
238
|
-
|
246
|
+
|
239
247
|
# The total number of pages available if the results are paginated.
|
240
|
-
#
|
248
|
+
#
|
241
249
|
# @return [Integer]
|
242
|
-
#
|
250
|
+
#
|
243
251
|
def total_pages
|
244
252
|
populate
|
245
253
|
return 0 if @results[:total].nil?
|
246
|
-
|
254
|
+
|
247
255
|
@total_pages ||= (@results[:total] / per_page.to_f).ceil
|
248
256
|
end
|
249
257
|
# Compatibility with kaminari and older versions of will_paginate
|
250
258
|
alias_method :page_count, :total_pages
|
251
259
|
alias_method :num_pages, :total_pages
|
252
|
-
|
260
|
+
|
253
261
|
# Query time taken
|
254
|
-
#
|
262
|
+
#
|
255
263
|
# @return [Integer]
|
256
264
|
#
|
257
265
|
def query_time
|
@@ -262,34 +270,37 @@ module ThinkingSphinx
|
|
262
270
|
end
|
263
271
|
|
264
272
|
# The total number of search results available.
|
265
|
-
#
|
273
|
+
#
|
266
274
|
# @return [Integer]
|
267
|
-
#
|
275
|
+
#
|
268
276
|
def total_entries
|
269
277
|
populate
|
270
278
|
return 0 if @results[:total_found].nil?
|
271
|
-
|
279
|
+
|
272
280
|
@total_entries ||= @results[:total_found]
|
273
281
|
end
|
274
|
-
|
282
|
+
|
283
|
+
# Compatibility with kaminari
|
284
|
+
alias_method :total_count, :total_entries
|
285
|
+
|
275
286
|
# The current page's offset, based on the number of records per page.
|
276
|
-
# Or explicit :offset if given.
|
277
|
-
#
|
287
|
+
# Or explicit :offset if given.
|
288
|
+
#
|
278
289
|
# @return [Integer]
|
279
|
-
#
|
290
|
+
#
|
280
291
|
def offset
|
281
292
|
@options[:offset] || ((current_page - 1) * per_page)
|
282
293
|
end
|
283
|
-
|
294
|
+
|
284
295
|
def indexes
|
285
296
|
return options[:index] if options[:index]
|
286
297
|
return '*' if classes.empty?
|
287
|
-
|
298
|
+
|
288
299
|
classes.collect { |klass|
|
289
300
|
klass.sphinx_index_names
|
290
301
|
}.flatten.uniq.join(',')
|
291
302
|
end
|
292
|
-
|
303
|
+
|
293
304
|
def each_with_groupby_and_count(&block)
|
294
305
|
populate
|
295
306
|
results[:matches].each_with_index do |match, index|
|
@@ -299,42 +310,44 @@ module ThinkingSphinx
|
|
299
310
|
end
|
300
311
|
end
|
301
312
|
alias_method :each_with_group_and_count, :each_with_groupby_and_count
|
302
|
-
|
313
|
+
|
303
314
|
def each_with_weighting(&block)
|
304
315
|
populate
|
305
316
|
results[:matches].each_with_index do |match, index|
|
306
317
|
yield self[index], match[:weight]
|
307
318
|
end
|
308
319
|
end
|
309
|
-
|
320
|
+
|
310
321
|
def each_with_match(&block)
|
311
322
|
populate
|
312
323
|
results[:matches].each_with_index do |match, index|
|
313
324
|
yield self[index], match
|
314
325
|
end
|
315
326
|
end
|
316
|
-
|
327
|
+
|
317
328
|
def excerpt_for(string, model = nil)
|
318
329
|
if model.nil? && one_class
|
319
330
|
model ||= one_class
|
320
331
|
end
|
321
|
-
|
332
|
+
|
322
333
|
populate
|
334
|
+
|
335
|
+
index = options[:index] || "#{model.core_index_names.first}"
|
323
336
|
client.excerpts(
|
324
337
|
{
|
325
338
|
:docs => [string.to_s],
|
326
339
|
:words => results[:words].keys.join(' '),
|
327
|
-
:index =>
|
340
|
+
:index => index.split(',').first.strip
|
328
341
|
}.merge(options[:excerpt_options] || {})
|
329
342
|
).first
|
330
343
|
end
|
331
|
-
|
344
|
+
|
332
345
|
def search(*args)
|
333
346
|
args << args.extract_options!.merge(:ignore_default => true)
|
334
347
|
merge_search ThinkingSphinx::Search.new(*args), self.args, options
|
335
348
|
self
|
336
349
|
end
|
337
|
-
|
350
|
+
|
338
351
|
def search_for_ids(*args)
|
339
352
|
args << args.extract_options!.merge(
|
340
353
|
:ignore_default => true,
|
@@ -343,45 +356,45 @@ module ThinkingSphinx
|
|
343
356
|
merge_search ThinkingSphinx::Search.new(*args), self.args, options
|
344
357
|
self
|
345
358
|
end
|
346
|
-
|
359
|
+
|
347
360
|
def facets(*args)
|
348
361
|
options = args.extract_options!
|
349
362
|
merge_search self, args, options
|
350
363
|
args << options
|
351
|
-
|
364
|
+
|
352
365
|
ThinkingSphinx::FacetSearch.new(*args)
|
353
366
|
end
|
354
|
-
|
367
|
+
|
355
368
|
def client
|
356
369
|
client = options[:client] || config.client
|
357
|
-
|
370
|
+
|
358
371
|
prepare client
|
359
372
|
end
|
360
|
-
|
373
|
+
|
361
374
|
def append_to(client)
|
362
375
|
prepare client
|
363
376
|
client.append_query query, indexes, comment
|
364
377
|
client.reset
|
365
378
|
end
|
366
|
-
|
379
|
+
|
367
380
|
def populate_from_queue(results)
|
368
381
|
return if @populated
|
369
382
|
@populated = true
|
370
383
|
@results = results
|
371
|
-
|
384
|
+
|
372
385
|
compose_results
|
373
386
|
end
|
374
|
-
|
387
|
+
|
375
388
|
private
|
376
|
-
|
389
|
+
|
377
390
|
def config
|
378
391
|
ThinkingSphinx::Configuration.instance
|
379
392
|
end
|
380
|
-
|
393
|
+
|
381
394
|
def populate
|
382
395
|
return if @populated
|
383
396
|
@populated = true
|
384
|
-
|
397
|
+
|
385
398
|
retry_on_stale_index do
|
386
399
|
begin
|
387
400
|
log "Querying: '#{query}'"
|
@@ -390,9 +403,9 @@ module ThinkingSphinx
|
|
390
403
|
}
|
391
404
|
log "Found #{@results[:total_found]} results", :debug,
|
392
405
|
"Sphinx (#{sprintf("%f", runtime)}s)"
|
393
|
-
|
406
|
+
|
394
407
|
log "Sphinx Daemon returned warning: #{warning}", :error if warning?
|
395
|
-
|
408
|
+
|
396
409
|
if error?
|
397
410
|
log "Sphinx Daemon returned error: #{error}", :error
|
398
411
|
raise SphinxError.new(error, @results) unless options[:ignore_errors]
|
@@ -401,7 +414,7 @@ module ThinkingSphinx
|
|
401
414
|
raise ThinkingSphinx::ConnectionError,
|
402
415
|
'Connection to Sphinx Daemon (searchd) failed.'
|
403
416
|
end
|
404
|
-
|
417
|
+
|
405
418
|
compose_results
|
406
419
|
end
|
407
420
|
end
|
@@ -418,13 +431,13 @@ module ThinkingSphinx
|
|
418
431
|
add_matching_fields if client.rank_mode == :fieldmask
|
419
432
|
end
|
420
433
|
end
|
421
|
-
|
434
|
+
|
422
435
|
def compose_ids_results
|
423
436
|
replace @results[:matches].collect { |match|
|
424
437
|
match[:attributes]['sphinx_internal_id']
|
425
438
|
}
|
426
439
|
end
|
427
|
-
|
440
|
+
|
428
441
|
def compose_only_results
|
429
442
|
replace @results[:matches].collect { |match|
|
430
443
|
case only = options[:only]
|
@@ -440,30 +453,30 @@ module ThinkingSphinx
|
|
440
453
|
end
|
441
454
|
}
|
442
455
|
end
|
443
|
-
|
456
|
+
|
444
457
|
def add_excerpter
|
445
458
|
each do |object|
|
446
459
|
next if object.nil?
|
447
|
-
|
460
|
+
|
448
461
|
object.excerpts = ThinkingSphinx::Excerpter.new self, object
|
449
462
|
end
|
450
463
|
end
|
451
|
-
|
464
|
+
|
452
465
|
def add_sphinx_attributes
|
453
466
|
each do |object|
|
454
467
|
next if object.nil?
|
455
|
-
|
468
|
+
|
456
469
|
match = match_hash object
|
457
470
|
next if match.nil?
|
458
|
-
|
471
|
+
|
459
472
|
object.sphinx_attributes = match[:attributes]
|
460
473
|
end
|
461
474
|
end
|
462
|
-
|
475
|
+
|
463
476
|
def add_matching_fields
|
464
477
|
each do |object|
|
465
478
|
next if object.nil?
|
466
|
-
|
479
|
+
|
467
480
|
match = match_hash object
|
468
481
|
next if match.nil?
|
469
482
|
object.matching_fields = ThinkingSphinx::Search.matching_fields(
|
@@ -471,21 +484,21 @@ module ThinkingSphinx
|
|
471
484
|
)
|
472
485
|
end
|
473
486
|
end
|
474
|
-
|
487
|
+
|
475
488
|
def match_hash(object)
|
476
489
|
@results[:matches].detect { |match|
|
477
490
|
class_crc = object.class.name
|
478
491
|
class_crc = object.class.to_crc32 if Riddle.loaded_version.to_i < 2
|
479
|
-
|
492
|
+
|
480
493
|
match[:attributes]['sphinx_internal_id'] == object.
|
481
494
|
primary_key_for_sphinx &&
|
482
495
|
match[:attributes][crc_attribute] == class_crc
|
483
496
|
}
|
484
497
|
end
|
485
|
-
|
498
|
+
|
486
499
|
def self.log(message, method = :debug, identifier = 'Sphinx')
|
487
500
|
return if ::ActiveRecord::Base.logger.nil?
|
488
|
-
|
501
|
+
|
489
502
|
info = ''
|
490
503
|
if ::ActiveRecord::Base.colorize_logging
|
491
504
|
identifier_color, message_color = "4;32;1", "0" # 0;1 = Bold
|
@@ -494,20 +507,20 @@ module ThinkingSphinx
|
|
494
507
|
else
|
495
508
|
info = "#{identifier} #{message}"
|
496
509
|
end
|
497
|
-
|
510
|
+
|
498
511
|
::ActiveRecord::Base.logger.send method, info
|
499
512
|
end
|
500
|
-
|
513
|
+
|
501
514
|
def log(*args)
|
502
515
|
self.class.log(*args)
|
503
516
|
end
|
504
|
-
|
517
|
+
|
505
518
|
def prepare(client)
|
506
519
|
index_options = {}
|
507
520
|
if one_class && one_class.sphinx_indexes && one_class.sphinx_indexes.first
|
508
521
|
index_options = one_class.sphinx_indexes.first.local_options
|
509
522
|
end
|
510
|
-
|
523
|
+
|
511
524
|
[
|
512
525
|
:max_matches, :group_by, :group_function, :group_clause,
|
513
526
|
:group_distinct, :id_range, :cut_off, :retry_count, :retry_delay,
|
@@ -519,7 +532,7 @@ module ThinkingSphinx
|
|
519
532
|
|
520
533
|
# treated non-standard as :select is already used for AR queries
|
521
534
|
client.select = options[:sphinx_select] || '*'
|
522
|
-
|
535
|
+
|
523
536
|
client.limit = per_page
|
524
537
|
client.offset = offset
|
525
538
|
client.match_mode = match_mode
|
@@ -530,62 +543,62 @@ module ThinkingSphinx
|
|
530
543
|
client.group_function = group_function if group_function
|
531
544
|
client.index_weights = index_weights
|
532
545
|
client.anchor = anchor
|
533
|
-
|
546
|
+
|
534
547
|
client
|
535
548
|
end
|
536
|
-
|
549
|
+
|
537
550
|
def retry_on_stale_index(&block)
|
538
551
|
stale_ids = []
|
539
552
|
retries = stale_retries
|
540
|
-
|
553
|
+
|
541
554
|
begin
|
542
555
|
options[:raise_on_stale] = retries > 0
|
543
556
|
block.call
|
544
|
-
|
557
|
+
|
545
558
|
# If ThinkingSphinx::Search#instances_from_matches found records in
|
546
559
|
# Sphinx but not in the DB and the :raise_on_stale option is set, this
|
547
560
|
# exception is raised. We retry a limited number of times, excluding the
|
548
561
|
# stale ids from the search.
|
549
562
|
rescue StaleIdsException => err
|
550
563
|
retries -= 1
|
551
|
-
|
564
|
+
|
552
565
|
# For logging
|
553
566
|
stale_ids |= err.ids
|
554
567
|
# ID exclusion
|
555
568
|
options[:without_ids] = Array(options[:without_ids]) | err.ids
|
556
|
-
|
569
|
+
|
557
570
|
log 'Sphinx Stale Ids (%s %s left): %s' % [
|
558
571
|
retries, (retries == 1 ? 'try' : 'tries'), stale_ids.join(', ')
|
559
572
|
]
|
560
573
|
retry
|
561
574
|
end
|
562
575
|
end
|
563
|
-
|
576
|
+
|
564
577
|
def classes
|
565
578
|
@classes ||= options[:classes] || []
|
566
579
|
end
|
567
|
-
|
580
|
+
|
568
581
|
def one_class
|
569
582
|
@one_class ||= classes.length != 1 ? nil : classes.first
|
570
583
|
end
|
571
|
-
|
584
|
+
|
572
585
|
def query
|
573
586
|
@query ||= begin
|
574
587
|
q = @args.join(' ') << conditions_as_query
|
575
588
|
(options[:star] ? star_query(q) : q).strip
|
576
589
|
end
|
577
590
|
end
|
578
|
-
|
591
|
+
|
579
592
|
def conditions_as_query
|
580
593
|
return '' if @options[:conditions].blank?
|
581
|
-
|
594
|
+
|
582
595
|
' ' + @options[:conditions].keys.collect { |key|
|
583
596
|
"@#{key} #{options[:conditions][key]}"
|
584
597
|
}.join(' ')
|
585
598
|
end
|
586
|
-
|
599
|
+
|
587
600
|
def star_query(query)
|
588
|
-
token = options[:star].is_a?(Regexp) ? options[:star] :
|
601
|
+
token = options[:star].is_a?(Regexp) ? options[:star] : default_star_token
|
589
602
|
|
590
603
|
query.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/u) do
|
591
604
|
pre, proper, post = $`, $&, $'
|
@@ -602,15 +615,25 @@ module ThinkingSphinx
|
|
602
615
|
end
|
603
616
|
end
|
604
617
|
end
|
605
|
-
|
618
|
+
|
619
|
+
if Regexp.instance_methods.include?(:encoding)
|
620
|
+
DefaultStarToken = Regexp.new('\p{Word}+')
|
621
|
+
else
|
622
|
+
DefaultStarToken = Regexp.new('\w+', nil, 'u')
|
623
|
+
end
|
624
|
+
|
625
|
+
def default_star_token
|
626
|
+
DefaultStarToken
|
627
|
+
end
|
628
|
+
|
606
629
|
def comment
|
607
630
|
options[:comment] || ''
|
608
631
|
end
|
609
|
-
|
632
|
+
|
610
633
|
def match_mode
|
611
634
|
options[:match_mode] || (options[:conditions].blank? ? :all : :extended)
|
612
635
|
end
|
613
|
-
|
636
|
+
|
614
637
|
def sort_mode
|
615
638
|
@sort_mode ||= case options[:sort_mode]
|
616
639
|
when :asc
|
@@ -630,7 +653,7 @@ module ThinkingSphinx
|
|
630
653
|
options[:sort_mode]
|
631
654
|
end
|
632
655
|
end
|
633
|
-
|
656
|
+
|
634
657
|
def sort_by
|
635
658
|
case @sort_by = (options[:sort_by] || options[:order])
|
636
659
|
when String
|
@@ -642,28 +665,28 @@ module ThinkingSphinx
|
|
642
665
|
''
|
643
666
|
end
|
644
667
|
end
|
645
|
-
|
668
|
+
|
646
669
|
def field_names
|
647
670
|
return [] unless one_class
|
648
|
-
|
671
|
+
|
649
672
|
one_class.sphinx_indexes.collect { |index|
|
650
673
|
index.fields.collect { |field| field.unique_name }
|
651
674
|
}.flatten
|
652
675
|
end
|
653
|
-
|
676
|
+
|
654
677
|
def sorted_fields_to_attributes(order_string)
|
655
678
|
field_names.each { |field|
|
656
679
|
order_string.gsub!(/(^|\s)#{field}(,?\s|$)/) { |match|
|
657
680
|
match.gsub field.to_s, field.to_s.concat("_sort")
|
658
681
|
}
|
659
682
|
}
|
660
|
-
|
683
|
+
|
661
684
|
order_string
|
662
685
|
end
|
663
|
-
|
686
|
+
|
664
687
|
# Turn :index_weights => { "foo" => 2, User => 1 } into :index_weights =>
|
665
688
|
# { "foo" => 2, "user_core" => 1, "user_delta" => 1 }
|
666
|
-
#
|
689
|
+
#
|
667
690
|
def index_weights
|
668
691
|
weights = options[:index_weights] || {}
|
669
692
|
weights.keys.inject({}) do |hash, key|
|
@@ -674,37 +697,37 @@ module ThinkingSphinx
|
|
674
697
|
else
|
675
698
|
hash[key] = weights[key]
|
676
699
|
end
|
677
|
-
|
700
|
+
|
678
701
|
hash
|
679
702
|
end
|
680
703
|
end
|
681
|
-
|
704
|
+
|
682
705
|
def group_by
|
683
706
|
options[:group] ? options[:group].to_s : nil
|
684
707
|
end
|
685
|
-
|
708
|
+
|
686
709
|
def group_function
|
687
710
|
options[:group] ? :attr : nil
|
688
711
|
end
|
689
|
-
|
712
|
+
|
690
713
|
def internal_filters
|
691
714
|
filters = [Riddle::Client::Filter.new('sphinx_deleted', [0])]
|
692
|
-
|
715
|
+
|
693
716
|
class_crcs = classes.collect { |klass|
|
694
717
|
klass.to_crc32s
|
695
718
|
}.flatten
|
696
|
-
|
719
|
+
|
697
720
|
unless class_crcs.empty?
|
698
721
|
filters << Riddle::Client::Filter.new('class_crc', class_crcs)
|
699
722
|
end
|
700
|
-
|
723
|
+
|
701
724
|
filters << Riddle::Client::Filter.new(
|
702
725
|
'sphinx_internal_id', filter_value(options[:without_ids]), true
|
703
|
-
)
|
704
|
-
|
726
|
+
) unless options[:without_ids].nil? || options[:without_ids].empty?
|
727
|
+
|
705
728
|
filters
|
706
729
|
end
|
707
|
-
|
730
|
+
|
708
731
|
def filters
|
709
732
|
internal_filters +
|
710
733
|
(options[:with] || {}).collect { |attrib, value|
|
@@ -724,7 +747,7 @@ module ThinkingSphinx
|
|
724
747
|
}
|
725
748
|
}.flatten
|
726
749
|
end
|
727
|
-
|
750
|
+
|
728
751
|
# When passed a Time instance, returns the integer timestamp.
|
729
752
|
def filter_value(value)
|
730
753
|
case value
|
@@ -740,10 +763,10 @@ module ThinkingSphinx
|
|
740
763
|
Array(value)
|
741
764
|
end
|
742
765
|
end
|
743
|
-
|
766
|
+
|
744
767
|
def anchor
|
745
768
|
return {} unless options[:geo] || (options[:lat] && options[:lng])
|
746
|
-
|
769
|
+
|
747
770
|
{
|
748
771
|
:latitude => options[:geo] ? options[:geo].first : options[:lat],
|
749
772
|
:longitude => options[:geo] ? options[:geo].last : options[:lng],
|
@@ -751,43 +774,43 @@ module ThinkingSphinx
|
|
751
774
|
:longitude_attribute => longitude_attr.to_s
|
752
775
|
}
|
753
776
|
end
|
754
|
-
|
777
|
+
|
755
778
|
def latitude_attr
|
756
779
|
options[:latitude_attr] ||
|
757
780
|
index_option(:latitude_attr) ||
|
758
781
|
attribute(:lat, :latitude)
|
759
782
|
end
|
760
|
-
|
783
|
+
|
761
784
|
def longitude_attr
|
762
785
|
options[:longitude_attr] ||
|
763
786
|
index_option(:longitude_attr) ||
|
764
787
|
attribute(:lon, :lng, :longitude)
|
765
788
|
end
|
766
|
-
|
789
|
+
|
767
790
|
def index_option(key)
|
768
791
|
return nil unless one_class
|
769
|
-
|
792
|
+
|
770
793
|
one_class.sphinx_indexes.collect { |index|
|
771
794
|
index.local_options[key]
|
772
795
|
}.compact.first
|
773
796
|
end
|
774
|
-
|
797
|
+
|
775
798
|
def attribute(*keys)
|
776
799
|
return nil unless one_class
|
777
|
-
|
800
|
+
|
778
801
|
keys.detect { |key|
|
779
802
|
attributes.include?(key)
|
780
803
|
}
|
781
804
|
end
|
782
|
-
|
805
|
+
|
783
806
|
def attributes
|
784
807
|
return [] unless one_class
|
785
|
-
|
808
|
+
|
786
809
|
attributes = one_class.sphinx_indexes.collect { |index|
|
787
810
|
index.attributes.collect { |attrib| attrib.unique_name }
|
788
811
|
}.flatten
|
789
812
|
end
|
790
|
-
|
813
|
+
|
791
814
|
def stale_retries
|
792
815
|
case options[:retry_stale]
|
793
816
|
when TrueClass
|
@@ -798,10 +821,10 @@ module ThinkingSphinx
|
|
798
821
|
options[:retry_stale].to_i
|
799
822
|
end
|
800
823
|
end
|
801
|
-
|
824
|
+
|
802
825
|
def include_for_class(klass)
|
803
826
|
includes = options[:include] || klass.sphinx_index_options[:include]
|
804
|
-
|
827
|
+
|
805
828
|
case includes
|
806
829
|
when NilClass
|
807
830
|
nil
|
@@ -829,7 +852,7 @@ module ThinkingSphinx
|
|
829
852
|
end
|
830
853
|
scoped_array.empty? ? nil : scoped_array
|
831
854
|
end
|
832
|
-
|
855
|
+
|
833
856
|
def include_from_hash(hash, klass)
|
834
857
|
scoped_hash = {}
|
835
858
|
hash.keys.each do |key|
|
@@ -837,7 +860,7 @@ module ThinkingSphinx
|
|
837
860
|
end
|
838
861
|
scoped_hash.empty? ? nil : scoped_hash
|
839
862
|
end
|
840
|
-
|
863
|
+
|
841
864
|
def instances_from_class(klass, matches)
|
842
865
|
index_options = klass.sphinx_index_options
|
843
866
|
|
@@ -852,7 +875,7 @@ module ThinkingSphinx
|
|
852
875
|
) : []
|
853
876
|
|
854
877
|
# Raise an exception if we find records in Sphinx but not in the DB, so
|
855
|
-
# the search method can retry without them. See
|
878
|
+
# the search method can retry without them. See
|
856
879
|
# ThinkingSphinx::Search.retry_search_on_stale_index.
|
857
880
|
if options[:raise_on_stale] && instances.length < ids.length
|
858
881
|
stale_ids = ids - instances.map { |i| i.id }
|
@@ -869,13 +892,13 @@ module ThinkingSphinx
|
|
869
892
|
end
|
870
893
|
}
|
871
894
|
end
|
872
|
-
|
895
|
+
|
873
896
|
# Group results by class and call #find(:all) once for each group to reduce
|
874
897
|
# the number of #find's in multi-model searches.
|
875
|
-
#
|
898
|
+
#
|
876
899
|
def instances_from_matches
|
877
900
|
return single_class_results if one_class
|
878
|
-
|
901
|
+
|
879
902
|
groups = results[:matches].group_by { |match|
|
880
903
|
match[:attributes][crc_attribute]
|
881
904
|
}
|
@@ -884,7 +907,7 @@ module ThinkingSphinx
|
|
884
907
|
instances_from_class(class_from_crc(crc), group)
|
885
908
|
)
|
886
909
|
end
|
887
|
-
|
910
|
+
|
888
911
|
results[:matches].collect do |match|
|
889
912
|
groups.detect { |crc, group|
|
890
913
|
crc == match[:attributes][crc_attribute]
|
@@ -893,11 +916,11 @@ module ThinkingSphinx
|
|
893
916
|
}
|
894
917
|
end
|
895
918
|
end
|
896
|
-
|
919
|
+
|
897
920
|
def single_class_results
|
898
921
|
instances_from_class one_class, results[:matches]
|
899
922
|
end
|
900
|
-
|
923
|
+
|
901
924
|
def class_from_crc(crc)
|
902
925
|
if Riddle.loaded_version.to_i < 2
|
903
926
|
config.models_by_crc[crc].constantize
|
@@ -905,7 +928,7 @@ module ThinkingSphinx
|
|
905
928
|
crc.constantize
|
906
929
|
end
|
907
930
|
end
|
908
|
-
|
931
|
+
|
909
932
|
def each_with_attribute(attribute, &block)
|
910
933
|
populate
|
911
934
|
results[:matches].each_with_index do |match, index|
|
@@ -913,25 +936,25 @@ module ThinkingSphinx
|
|
913
936
|
(match[:attributes][attribute] || match[:attributes]["@#{attribute}"])
|
914
937
|
end
|
915
938
|
end
|
916
|
-
|
939
|
+
|
917
940
|
def is_scope?(method)
|
918
941
|
one_class && one_class.sphinx_scopes.include?(method)
|
919
942
|
end
|
920
|
-
|
943
|
+
|
921
944
|
# Adds the default_sphinx_scope if set.
|
922
945
|
def add_default_scope
|
923
946
|
return unless one_class && one_class.has_default_sphinx_scope?
|
924
947
|
add_scope(one_class.get_default_sphinx_scope.to_sym)
|
925
948
|
end
|
926
|
-
|
949
|
+
|
927
950
|
def add_scope(method, *args, &block)
|
928
951
|
method = "#{method}_without_default".to_sym
|
929
952
|
merge_search one_class.send(method, *args, &block), self.args, options
|
930
953
|
end
|
931
|
-
|
954
|
+
|
932
955
|
def merge_search(search, args, options)
|
933
956
|
search.args.each { |arg| args << arg }
|
934
|
-
|
957
|
+
|
935
958
|
search.options.keys.each do |key|
|
936
959
|
if HashOptions.include?(key)
|
937
960
|
options[key] ||= {}
|
@@ -945,18 +968,18 @@ module ThinkingSphinx
|
|
945
968
|
end
|
946
969
|
end
|
947
970
|
end
|
948
|
-
|
971
|
+
|
949
972
|
def scoped_count
|
950
973
|
return self.total_entries if(@options[:ids_only] || @options[:only])
|
951
|
-
|
974
|
+
|
952
975
|
@options[:ids_only] = true
|
953
976
|
results_count = self.total_entries
|
954
977
|
@options[:ids_only] = false
|
955
978
|
@populated = false
|
956
|
-
|
979
|
+
|
957
980
|
results_count
|
958
981
|
end
|
959
|
-
|
982
|
+
|
960
983
|
def crc_attribute
|
961
984
|
Riddle.loaded_version.to_i < 2 ? 'class_crc' : 'sphinx_internal_class'
|
962
985
|
end
|