DrMark-thinking-sphinx 1.1.15 → 1.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +22 -0
- data/VERSION.yml +4 -0
- data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
- data/lib/thinking_sphinx/active_record.rb +27 -7
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +9 -3
- data/lib/thinking_sphinx/association.rb +4 -1
- data/lib/thinking_sphinx/attribute.rb +91 -30
- data/lib/thinking_sphinx/configuration.rb +51 -12
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +2 -2
- data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
- data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +3 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +25 -8
- data/lib/thinking_sphinx/excerpter.rb +22 -0
- data/lib/thinking_sphinx/facet.rb +1 -1
- data/lib/thinking_sphinx/facet_search.rb +134 -0
- data/lib/thinking_sphinx/index.rb +2 -1
- data/lib/thinking_sphinx/rails_additions.rb +14 -0
- data/lib/thinking_sphinx/search.rb +599 -658
- data/lib/thinking_sphinx/search_methods.rb +421 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +1 -1
- data/lib/thinking_sphinx/source/sql.rb +17 -13
- data/lib/thinking_sphinx/source.rb +6 -6
- data/lib/thinking_sphinx/tasks.rb +42 -8
- data/lib/thinking_sphinx.rb +82 -54
- data/rails/init.rb +14 -0
- data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +5 -5
- data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
- data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
- data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +51 -31
- data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +4 -5
- data/spec/lib/thinking_sphinx/attribute_spec.rb +465 -0
- data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +161 -29
- data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
- data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
- data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
- data/spec/{unit → lib}/thinking_sphinx/facet_spec.rb +24 -0
- data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +8 -8
- data/spec/{unit → lib}/thinking_sphinx/index/builder_spec.rb +6 -2
- data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
- data/spec/lib/thinking_sphinx/index_spec.rb +45 -0
- data/spec/{unit → lib}/thinking_sphinx/rails_additions_spec.rb +25 -5
- data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
- data/spec/lib/thinking_sphinx/search_spec.rb +960 -0
- data/spec/{unit → lib}/thinking_sphinx/source_spec.rb +63 -2
- data/spec/{unit → lib}/thinking_sphinx_spec.rb +32 -4
- data/tasks/distribution.rb +36 -35
- data/vendor/riddle/lib/riddle/client/message.rb +4 -3
- data/vendor/riddle/lib/riddle/client.rb +3 -0
- data/vendor/riddle/lib/riddle/configuration/section.rb +8 -2
- data/vendor/riddle/lib/riddle/controller.rb +17 -7
- data/vendor/riddle/lib/riddle.rb +1 -1
- metadata +79 -83
- data/lib/thinking_sphinx/active_record/search.rb +0 -57
- data/lib/thinking_sphinx/collection.rb +0 -148
- data/lib/thinking_sphinx/facet_collection.rb +0 -59
- data/lib/thinking_sphinx/search/facets.rb +0 -98
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
- data/spec/unit/thinking_sphinx/attribute_spec.rb +0 -232
- data/spec/unit/thinking_sphinx/collection_spec.rb +0 -14
- data/spec/unit/thinking_sphinx/facet_collection_spec.rb +0 -64
- data/spec/unit/thinking_sphinx/index_spec.rb +0 -139
- data/spec/unit/thinking_sphinx/search_spec.rb +0 -130
data/README.textile
CHANGED
@@ -25,11 +25,18 @@ Then install the ginger gem. The steps are the same, except that you might need
|
|
25
25
|
cd ginger
|
26
26
|
rake gem
|
27
27
|
sudo gem install pkg/ginger-1.1.0.gem
|
28
|
+
|
29
|
+
Alternatively, install the ginger gem directly from the freelancing-god github repository
|
30
|
+
|
31
|
+
sudo gem sources -a http://gems.github.com
|
32
|
+
sudo gem install freelancing-god-ginger
|
28
33
|
|
29
34
|
Then set up your database:
|
30
35
|
|
31
36
|
cp spec/fixtures/database.yml.default spec/fixtures/database.yml
|
32
37
|
mysqladmin -u root create thinking_sphinx
|
38
|
+
|
39
|
+
This last step can be done automatically by the contribute.rb script if all dependencies are met.
|
33
40
|
|
34
41
|
Make sure you don't have another Sphinx daemon (searchd) running. If you do, quit it with "rake ts:stop"
|
35
42
|
in the app root.
|
@@ -125,3 +132,18 @@ Since I first released this library, there's been quite a few people who have su
|
|
125
132
|
* Eric Lindvall
|
126
133
|
* Lawrence Pit
|
127
134
|
* Mike Bailey
|
135
|
+
* Bill Leeper
|
136
|
+
* Michael Reinsch
|
137
|
+
* Anderson Dias
|
138
|
+
* Jerome Riga
|
139
|
+
* Tien Dung
|
140
|
+
* Johannes Kaefer
|
141
|
+
* Paul Campbell
|
142
|
+
* Matthew Beale
|
143
|
+
* Tom Simnett
|
144
|
+
* Erik Ostrom
|
145
|
+
* Ole Riesenberg
|
146
|
+
* Josh Kalderimis
|
147
|
+
* J.D. Hollis
|
148
|
+
* Jeffrey Chupp
|
149
|
+
* Rob Anderton
|
data/VERSION.yml
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module ActiveRecord
|
3
|
+
module Scopes
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
extend ThinkingSphinx::ActiveRecord::Scopes::ClassMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def sphinx_scope(method, &block)
|
12
|
+
@sphinx_scopes ||= []
|
13
|
+
@sphinx_scopes << method
|
14
|
+
|
15
|
+
metaclass.instance_eval do
|
16
|
+
define_method(method) do |*args|
|
17
|
+
options = {:classes => classes_option}
|
18
|
+
options.merge! block.call(*args)
|
19
|
+
|
20
|
+
ThinkingSphinx::Search.new(options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def sphinx_scopes
|
26
|
+
@sphinx_scopes || []
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_sphinx_scopes
|
30
|
+
sphinx_scopes.each do |scope|
|
31
|
+
metaclass.send(:undef_method, scope)
|
32
|
+
end
|
33
|
+
|
34
|
+
sphinx_scopes.clear
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'thinking_sphinx/active_record/attribute_updates'
|
2
2
|
require 'thinking_sphinx/active_record/delta'
|
3
|
-
require 'thinking_sphinx/active_record/search'
|
4
3
|
require 'thinking_sphinx/active_record/has_many_association'
|
4
|
+
require 'thinking_sphinx/active_record/scopes'
|
5
5
|
|
6
6
|
module ThinkingSphinx
|
7
7
|
# Core additions to ActiveRecord models - define_index for creating indexes
|
@@ -13,6 +13,15 @@ module ThinkingSphinx
|
|
13
13
|
base.class_eval do
|
14
14
|
class_inheritable_array :sphinx_indexes, :sphinx_facets
|
15
15
|
class << self
|
16
|
+
|
17
|
+
def set_sphinx_primary_key(attribute)
|
18
|
+
@sphinx_primary_key_attribute = attribute
|
19
|
+
end
|
20
|
+
|
21
|
+
def primary_key_for_sphinx
|
22
|
+
@sphinx_primary_key_attribute || primary_key
|
23
|
+
end
|
24
|
+
|
16
25
|
# Allows creation of indexes for Sphinx. If you don't do this, there
|
17
26
|
# isn't much point trying to search (or using this plugin at all,
|
18
27
|
# really).
|
@@ -80,7 +89,9 @@ module ThinkingSphinx
|
|
80
89
|
|
81
90
|
after_destroy :toggle_deleted
|
82
91
|
|
92
|
+
include ThinkingSphinx::SearchMethods
|
83
93
|
include ThinkingSphinx::ActiveRecord::AttributeUpdates
|
94
|
+
include ThinkingSphinx::ActiveRecord::Scopes
|
84
95
|
|
85
96
|
index
|
86
97
|
|
@@ -140,12 +151,12 @@ module ThinkingSphinx
|
|
140
151
|
ThinkingSphinx::AbstractAdapter.detect(self)
|
141
152
|
end
|
142
153
|
|
143
|
-
private
|
144
|
-
|
145
154
|
def sphinx_name
|
146
155
|
self.name.underscore.tr(':/\\', '_')
|
147
156
|
end
|
148
157
|
|
158
|
+
private
|
159
|
+
|
149
160
|
def sphinx_delta?
|
150
161
|
self.sphinx_indexes.any? { |index| index.delta? }
|
151
162
|
end
|
@@ -215,7 +226,6 @@ module ThinkingSphinx
|
|
215
226
|
end
|
216
227
|
|
217
228
|
base.send(:include, ThinkingSphinx::ActiveRecord::Delta)
|
218
|
-
base.send(:include, ThinkingSphinx::ActiveRecord::Search)
|
219
229
|
|
220
230
|
::ActiveRecord::Associations::HasManyAssociation.send(
|
221
231
|
:include, ThinkingSphinx::ActiveRecord::HasManyAssociation
|
@@ -264,13 +274,23 @@ module ThinkingSphinx
|
|
264
274
|
# nothing
|
265
275
|
end
|
266
276
|
|
277
|
+
# Returns the unique integer id for the object. This method uses the
|
278
|
+
# attribute hash to get around ActiveRecord always mapping the #id method
|
279
|
+
# to whatever the real primary key is (which may be a unique string hash).
|
280
|
+
#
|
281
|
+
# @return [Integer] Unique record id for the purposes of Sphinx.
|
282
|
+
#
|
283
|
+
def primary_key_for_sphinx
|
284
|
+
attributes[self.class.primary_key_for_sphinx.to_s]
|
285
|
+
end
|
286
|
+
|
267
287
|
def sphinx_document_id
|
268
|
-
|
288
|
+
primary_key_for_sphinx * ThinkingSphinx.indexed_models.size +
|
269
289
|
ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
|
270
290
|
end
|
271
|
-
|
291
|
+
|
272
292
|
private
|
273
|
-
|
293
|
+
|
274
294
|
def sphinx_index_name(suffix)
|
275
295
|
"#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
|
276
296
|
end
|
@@ -11,7 +11,12 @@ module ThinkingSphinx
|
|
11
11
|
|
12
12
|
def concatenate(clause, separator = ' ')
|
13
13
|
clause.split(', ').collect { |field|
|
14
|
-
|
14
|
+
case field
|
15
|
+
when /COALESCE/, "'')"
|
16
|
+
field
|
17
|
+
else
|
18
|
+
"COALESCE(CAST(#{field} as varchar), '')"
|
19
|
+
end
|
15
20
|
}.join(" || '#{separator}' || ")
|
16
21
|
end
|
17
22
|
|
@@ -32,7 +37,8 @@ module ThinkingSphinx
|
|
32
37
|
end
|
33
38
|
|
34
39
|
def convert_nulls(clause, default = '')
|
35
|
-
default = "'#{default}'"
|
40
|
+
default = "'#{default}'" if default.is_a?(String)
|
41
|
+
default = 'NULL' if default.nil?
|
36
42
|
|
37
43
|
"COALESCE(#{clause}, #{default})"
|
38
44
|
end
|
@@ -127,4 +133,4 @@ module ThinkingSphinx
|
|
127
133
|
execute function, true
|
128
134
|
end
|
129
135
|
end
|
130
|
-
end
|
136
|
+
end
|
@@ -103,13 +103,16 @@ module ThinkingSphinx
|
|
103
103
|
if @reflection.options[:through]
|
104
104
|
@reflection.source_reflection.options[:foreign_key] ||
|
105
105
|
@reflection.source_reflection.primary_key_name
|
106
|
+
elsif @reflection.macro == :has_and_belongs_to_many
|
107
|
+
@reflection.association_foreign_key
|
106
108
|
else
|
107
109
|
nil
|
108
110
|
end
|
109
111
|
end
|
110
112
|
|
111
113
|
def table
|
112
|
-
if @reflection.options[:through]
|
114
|
+
if @reflection.options[:through] ||
|
115
|
+
@reflection.macro == :has_and_belongs_to_many
|
113
116
|
@join.aliased_join_table_name
|
114
117
|
else
|
115
118
|
@join.aliased_table_name
|
@@ -89,14 +89,21 @@ module ThinkingSphinx
|
|
89
89
|
def to_select_sql
|
90
90
|
return nil unless include_as_association?
|
91
91
|
|
92
|
+
separator = all_ints? || all_datetimes? || @crc ? ',' : ' '
|
93
|
+
|
92
94
|
clause = @columns.collect { |column|
|
93
95
|
part = column_with_prefix(column)
|
94
|
-
type
|
96
|
+
case type
|
97
|
+
when :string
|
98
|
+
adapter.convert_nulls(part)
|
99
|
+
when :datetime
|
100
|
+
adapter.cast_to_datetime(part)
|
101
|
+
else
|
102
|
+
part
|
103
|
+
end
|
95
104
|
}.join(', ')
|
96
105
|
|
97
|
-
|
98
|
-
|
99
|
-
clause = adapter.cast_to_datetime(clause) if type == :datetime
|
106
|
+
# clause = adapter.cast_to_datetime(clause) if type == :datetime
|
100
107
|
clause = adapter.crc(clause) if @crc
|
101
108
|
clause = adapter.concatenate(clause, separator) if concat_ws?
|
102
109
|
clause = adapter.group_concatenate(clause, separator) if is_many?
|
@@ -124,10 +131,10 @@ module ThinkingSphinx
|
|
124
131
|
# Special case is the multi-valued attribute that needs some
|
125
132
|
# extra configuration.
|
126
133
|
#
|
127
|
-
def config_value(offset = nil)
|
134
|
+
def config_value(offset = nil, delta = false)
|
128
135
|
if type == :multi
|
129
136
|
multi_config = include_as_association? ? "field" :
|
130
|
-
source_value(offset).gsub(/\
|
137
|
+
source_value(offset, delta).gsub(/\s+/m, " ").strip
|
131
138
|
"uint #{unique_name} from #{multi_config}"
|
132
139
|
else
|
133
140
|
unique_name
|
@@ -142,6 +149,8 @@ module ThinkingSphinx
|
|
142
149
|
def type
|
143
150
|
@type ||= begin
|
144
151
|
base_type = case
|
152
|
+
when is_many_datetimes?
|
153
|
+
:datetime
|
145
154
|
when is_many?, is_many_ints?
|
146
155
|
:multi
|
147
156
|
when @associations.values.flatten.length > 1
|
@@ -171,47 +180,61 @@ module ThinkingSphinx
|
|
171
180
|
end
|
172
181
|
|
173
182
|
def all_ints?
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
!column.nil? && column.type == :integer
|
180
|
-
}
|
181
|
-
}
|
183
|
+
all_of_type?(:integer)
|
184
|
+
end
|
185
|
+
|
186
|
+
def all_datetimes?
|
187
|
+
all_of_type?(:datetime, :date, :timestamp)
|
182
188
|
end
|
183
189
|
|
184
190
|
private
|
185
191
|
|
186
|
-
def source_value(offset)
|
192
|
+
def source_value(offset, delta)
|
187
193
|
if is_string?
|
188
|
-
"#{query_source.to_s.dasherize}; #{columns.first.__name}"
|
189
|
-
|
190
|
-
|
194
|
+
return "#{query_source.to_s.dasherize}; #{columns.first.__name}"
|
195
|
+
end
|
196
|
+
|
197
|
+
query = query(offset)
|
198
|
+
|
199
|
+
if query_source == :ranged_query
|
200
|
+
query += query_clause
|
201
|
+
query += " AND #{query_delta.strip}" if delta
|
202
|
+
"ranged-query; #{query}; #{range_query}"
|
191
203
|
else
|
192
|
-
|
204
|
+
query += "WHERE #{query_delta.strip}" if delta
|
205
|
+
"query; #{query}"
|
193
206
|
end
|
194
207
|
end
|
195
208
|
|
196
209
|
def query(offset)
|
197
|
-
|
198
|
-
|
210
|
+
base_assoc = base_association_for_mva
|
211
|
+
end_assoc = end_association_for_mva
|
212
|
+
raise "Could not determine SQL for MVA" if base_assoc.nil?
|
199
213
|
|
200
214
|
<<-SQL
|
201
|
-
SELECT #{foreign_key_for_mva
|
215
|
+
SELECT #{foreign_key_for_mva base_assoc}
|
202
216
|
#{ThinkingSphinx.unique_id_expression(offset)} AS #{quote_column('id')},
|
203
|
-
#{primary_key_for_mva(
|
204
|
-
FROM #{quote_table_name
|
217
|
+
#{primary_key_for_mva(end_assoc)} AS #{quote_column(unique_name)}
|
218
|
+
FROM #{quote_table_name base_assoc.table} #{association_joins}
|
205
219
|
SQL
|
206
220
|
end
|
207
221
|
|
208
222
|
def query_clause
|
209
|
-
foreign_key = foreign_key_for_mva
|
223
|
+
foreign_key = foreign_key_for_mva base_association_for_mva
|
210
224
|
"WHERE #{foreign_key} >= $start AND #{foreign_key} <= $end"
|
211
225
|
end
|
212
226
|
|
227
|
+
def query_delta
|
228
|
+
foreign_key = foreign_key_for_mva base_association_for_mva
|
229
|
+
<<-SQL
|
230
|
+
#{foreign_key} IN (SELECT #{quote_column model.primary_key}
|
231
|
+
FROM #{model.quoted_table_name}
|
232
|
+
WHERE #{@source.index.delta_object.clause(model, true)})
|
233
|
+
SQL
|
234
|
+
end
|
235
|
+
|
213
236
|
def range_query
|
214
|
-
assoc =
|
237
|
+
assoc = base_association_for_mva
|
215
238
|
foreign_key = foreign_key_for_mva assoc
|
216
239
|
"SELECT MIN(#{foreign_key}), MAX(#{foreign_key}) FROM #{quote_table_name assoc.table}"
|
217
240
|
end
|
@@ -226,23 +249,50 @@ FROM #{quote_table_name assoc.table}
|
|
226
249
|
quote_with_table assoc.table, assoc.reflection.primary_key_name
|
227
250
|
end
|
228
251
|
|
229
|
-
def
|
252
|
+
def end_association_for_mva
|
230
253
|
@association_for_mva ||= associations[columns.first].detect { |assoc|
|
231
254
|
assoc.has_column?(columns.first.__name)
|
232
255
|
}
|
233
256
|
end
|
234
257
|
|
258
|
+
def base_association_for_mva
|
259
|
+
@first_association_for_mva ||= begin
|
260
|
+
assoc = end_association_for_mva
|
261
|
+
while !assoc.parent.nil?
|
262
|
+
assoc = assoc.parent
|
263
|
+
end
|
264
|
+
|
265
|
+
assoc
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def association_joins
|
270
|
+
joins = []
|
271
|
+
assoc = end_association_for_mva
|
272
|
+
while assoc != base_association_for_mva
|
273
|
+
joins << assoc.to_sql
|
274
|
+
assoc = assoc.parent
|
275
|
+
end
|
276
|
+
|
277
|
+
joins.join(' ')
|
278
|
+
end
|
279
|
+
|
235
280
|
def is_many_ints?
|
236
281
|
concat_ws? && all_ints?
|
237
282
|
end
|
238
|
-
|
283
|
+
|
284
|
+
def is_many_datetimes?
|
285
|
+
is_many? && all_datetimes?
|
286
|
+
end
|
287
|
+
|
239
288
|
def type_from_database
|
240
289
|
klass = @associations.values.flatten.first ?
|
241
290
|
@associations.values.flatten.first.reflection.klass : @model
|
242
291
|
|
243
|
-
klass.columns.detect { |col|
|
292
|
+
column = klass.columns.detect { |col|
|
244
293
|
@columns.collect { |c| c.__name.to_s }.include? col.name
|
245
|
-
}
|
294
|
+
}
|
295
|
+
column.nil? ? nil : column.type
|
246
296
|
end
|
247
297
|
|
248
298
|
def translated_type_from_database
|
@@ -264,5 +314,16 @@ block:
|
|
264
314
|
MESSAGE
|
265
315
|
end
|
266
316
|
end
|
317
|
+
|
318
|
+
def all_of_type?(*column_types)
|
319
|
+
@columns.all? { |col|
|
320
|
+
klasses = @associations[col].empty? ? [@model] :
|
321
|
+
@associations[col].collect { |assoc| assoc.reflection.klass }
|
322
|
+
klasses.all? { |klass|
|
323
|
+
column = klass.columns.detect { |column| column.name == col.__name.to_s }
|
324
|
+
!column.nil? && column_types.include?(column.type)
|
325
|
+
}
|
326
|
+
}
|
327
|
+
end
|
267
328
|
end
|
268
329
|
end
|
@@ -19,12 +19,14 @@ module ThinkingSphinx
|
|
19
19
|
# min infix length:: 1
|
20
20
|
# mem limit:: 64M
|
21
21
|
# max matches:: 1000
|
22
|
-
# morphology::
|
22
|
+
# morphology:: nil
|
23
23
|
# charset type:: utf-8
|
24
24
|
# charset table:: nil
|
25
25
|
# ignore chars:: nil
|
26
26
|
# html strip:: false
|
27
27
|
# html remove elements:: ''
|
28
|
+
# searchd_binary_name:: searchd
|
29
|
+
# indexer_binary_name:: indexer
|
28
30
|
#
|
29
31
|
# If you want to change these settings, create a YAML file at
|
30
32
|
# config/sphinx.yml with settings for each environment, in a similar
|
@@ -32,7 +34,8 @@ module ThinkingSphinx
|
|
32
34
|
# searchd_log_file, query_log_file, pid_file, searchd_file_path, port,
|
33
35
|
# allow_star, enable_star, min_prefix_len, min_infix_len, mem_limit,
|
34
36
|
# max_matches, morphology, charset_type, charset_table, ignore_chars,
|
35
|
-
# html_strip, html_remove_elements, delayed_job_priority
|
37
|
+
# html_strip, html_remove_elements, delayed_job_priority,
|
38
|
+
# searchd_binary_name, indexer_binary_name.
|
36
39
|
#
|
37
40
|
# I think you've got the idea.
|
38
41
|
#
|
@@ -54,11 +57,13 @@ module ThinkingSphinx
|
|
54
57
|
min_infix_len min_prefix_len min_word_len mlock morphology ngram_chars
|
55
58
|
ngram_len phrase_boundary phrase_boundary_step preopen stopwords
|
56
59
|
wordforms )
|
60
|
+
|
61
|
+
CustomOptions = %w( disable_range )
|
57
62
|
|
58
63
|
attr_accessor :config_file, :searchd_log_file, :query_log_file,
|
59
64
|
:pid_file, :searchd_file_path, :address, :port, :allow_star,
|
60
65
|
:database_yml_file, :app_root, :bin_path, :model_directories,
|
61
|
-
:delayed_job_priority
|
66
|
+
:delayed_job_priority, :searchd_binary_name, :indexer_binary_name
|
62
67
|
|
63
68
|
attr_accessor :source_options, :index_options
|
64
69
|
|
@@ -71,10 +76,19 @@ module ThinkingSphinx
|
|
71
76
|
self.reset
|
72
77
|
end
|
73
78
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
|
79
|
+
def self.configure(&block)
|
80
|
+
yield instance
|
81
|
+
instance.reset(instance.app_root)
|
82
|
+
end
|
83
|
+
|
84
|
+
def reset(custom_app_root=nil)
|
85
|
+
if custom_app_root
|
86
|
+
self.app_root = custom_app_root
|
87
|
+
else
|
88
|
+
self.app_root = RAILS_ROOT if defined?(RAILS_ROOT)
|
89
|
+
self.app_root = Merb.root if defined?(Merb)
|
90
|
+
self.app_root ||= app_root
|
91
|
+
end
|
78
92
|
|
79
93
|
@configuration = Riddle::Configuration.new
|
80
94
|
@configuration.searchd.address = "127.0.0.1"
|
@@ -94,9 +108,11 @@ module ThinkingSphinx
|
|
94
108
|
|
95
109
|
self.source_options = {}
|
96
110
|
self.index_options = {
|
97
|
-
:charset_type => "utf-8"
|
98
|
-
:morphology => "stem_en"
|
111
|
+
:charset_type => "utf-8"
|
99
112
|
}
|
113
|
+
|
114
|
+
self.searchd_binary_name = "searchd"
|
115
|
+
self.indexer_binary_name = "indexer"
|
100
116
|
|
101
117
|
parse_config
|
102
118
|
|
@@ -141,6 +157,8 @@ module ThinkingSphinx
|
|
141
157
|
# messy dependencies issues).
|
142
158
|
#
|
143
159
|
def load_models
|
160
|
+
return if defined?(Rails) && Rails.configuration.cache_classes
|
161
|
+
|
144
162
|
self.model_directories.each do |base|
|
145
163
|
Dir["#{base}**/*.rb"].each do |file|
|
146
164
|
model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
|
@@ -156,6 +174,8 @@ module ThinkingSphinx
|
|
156
174
|
model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
|
157
175
|
rescue NameError
|
158
176
|
next
|
177
|
+
rescue StandardError
|
178
|
+
puts "Warning: Error loading #{file}"
|
159
179
|
end
|
160
180
|
end
|
161
181
|
end
|
@@ -201,6 +221,24 @@ module ThinkingSphinx
|
|
201
221
|
@configuration.searchd.query_log = file
|
202
222
|
end
|
203
223
|
|
224
|
+
def client
|
225
|
+
client = Riddle::Client.new address, port
|
226
|
+
client.max_matches = configuration.searchd.max_matches || 1000
|
227
|
+
client
|
228
|
+
end
|
229
|
+
|
230
|
+
def models_by_crc
|
231
|
+
@models_by_crc ||= begin
|
232
|
+
ThinkingSphinx.indexed_models.inject({}) do |hash, model|
|
233
|
+
hash[model.constantize.to_crc32] = model
|
234
|
+
Object.subclasses_of(model.constantize).each { |subclass|
|
235
|
+
hash[subclass.to_crc32] = subclass.name
|
236
|
+
}
|
237
|
+
hash
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
204
242
|
private
|
205
243
|
|
206
244
|
# Parse the config/sphinx.yml file - if it exists - then use the attribute
|
@@ -213,10 +251,11 @@ module ThinkingSphinx
|
|
213
251
|
conf = YAML::load(ERB.new(IO.read(path)).result)[environment]
|
214
252
|
|
215
253
|
conf.each do |key,value|
|
216
|
-
self.send("#{key}=", value) if self.
|
254
|
+
self.send("#{key}=", value) if self.respond_to?("#{key}=")
|
217
255
|
|
218
256
|
set_sphinx_setting self.source_options, key, value, SourceOptions
|
219
257
|
set_sphinx_setting self.index_options, key, value, IndexOptions
|
258
|
+
set_sphinx_setting self.index_options, key, value, CustomOptions
|
220
259
|
set_sphinx_setting @configuration.searchd, key, value
|
221
260
|
set_sphinx_setting @configuration.indexer, key, value
|
222
261
|
end unless conf.nil?
|
@@ -233,8 +272,8 @@ module ThinkingSphinx
|
|
233
272
|
if object.is_a?(Hash)
|
234
273
|
object[key.to_sym] = value if allowed.include?(key.to_s)
|
235
274
|
else
|
236
|
-
object.send("#{key}=", value) if object.
|
237
|
-
send("#{key}=", value) if self.
|
275
|
+
object.send("#{key}=", value) if object.respond_to?("#{key}")
|
276
|
+
send("#{key}=", value) if self.respond_to?("#{key}")
|
238
277
|
end
|
239
278
|
end
|
240
279
|
end
|
@@ -18,8 +18,8 @@ module ThinkingSphinx
|
|
18
18
|
config = ThinkingSphinx::Configuration.instance
|
19
19
|
rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
|
20
20
|
|
21
|
-
output = `#{config.bin_path}
|
22
|
-
output += `#{config.bin_path}
|
21
|
+
output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} #{delta_index_name model}`
|
22
|
+
output += `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} --merge #{core_index_name model} #{delta_index_name model} --merge-dst-range sphinx_deleted 0 0`
|
23
23
|
puts output unless ThinkingSphinx.suppress_delta_output?
|
24
24
|
|
25
25
|
true
|
@@ -17,7 +17,7 @@ module ThinkingSphinx
|
|
17
17
|
client = Riddle::Client.new config.address, config.port
|
18
18
|
rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
|
19
19
|
|
20
|
-
output = `#{config.bin_path}
|
20
|
+
output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} #{delta_index_name model}`
|
21
21
|
puts(output) unless ThinkingSphinx.suppress_delta_output?
|
22
22
|
|
23
23
|
client.update(
|
@@ -14,7 +14,7 @@ module ThinkingSphinx
|
|
14
14
|
config = ThinkingSphinx::Configuration.instance
|
15
15
|
client = Riddle::Client.new config.address, config.port
|
16
16
|
|
17
|
-
output = `#{config.bin_path}
|
17
|
+
output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} --rotate #{index}`
|
18
18
|
puts output unless ThinkingSphinx.suppress_delta_output?
|
19
19
|
|
20
20
|
true
|
@@ -8,6 +8,9 @@ module ThinkingSphinx
|
|
8
8
|
module Deltas
|
9
9
|
class DelayedDelta < ThinkingSphinx::Deltas::DefaultDelta
|
10
10
|
def index(model, instance = nil)
|
11
|
+
return true unless ThinkingSphinx.updates_enabled? && ThinkingSphinx.deltas_enabled?
|
12
|
+
return true if instance && !toggled(instance)
|
13
|
+
|
11
14
|
ThinkingSphinx::Deltas::Job.enqueue(
|
12
15
|
ThinkingSphinx::Deltas::DeltaJob.new(delta_index_name(model)),
|
13
16
|
ThinkingSphinx::Configuration.instance.delayed_job_priority
|
@@ -1,11 +1,26 @@
|
|
1
1
|
Capistrano::Configuration.instance(:must_exist).load do
|
2
2
|
namespace :thinking_sphinx do
|
3
3
|
namespace :install do
|
4
|
-
desc
|
4
|
+
desc <<-DESC
|
5
|
+
Install Sphinx by source
|
6
|
+
|
7
|
+
If Postgres is available, Sphinx will use it.
|
8
|
+
|
9
|
+
If the variable :thinking_sphinx_configure_args is set, it will
|
10
|
+
be passed to the Sphinx configure script. You can use this to
|
11
|
+
install Sphinx in a non-standard location:
|
12
|
+
|
13
|
+
set :thinking_sphinx_configure_args, "--prefix=$HOME/software"
|
14
|
+
DESC
|
15
|
+
|
5
16
|
task :sphinx do
|
6
17
|
with_postgres = false
|
7
|
-
|
8
|
-
|
18
|
+
begin
|
19
|
+
run "which pg_config" do |channel, stream, data|
|
20
|
+
with_postgres = !(data.nil? || data == "")
|
21
|
+
end
|
22
|
+
rescue Capistrano::CommandError => e
|
23
|
+
puts "Continuing despite error: #{e.message}"
|
9
24
|
end
|
10
25
|
|
11
26
|
args = []
|
@@ -14,14 +29,15 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
14
29
|
args << "--with-pgsql=#{data}"
|
15
30
|
end
|
16
31
|
end
|
17
|
-
|
32
|
+
args << fetch(:thinking_sphinx_configure_args, '')
|
33
|
+
|
18
34
|
commands = <<-CMD
|
19
35
|
wget -q http://www.sphinxsearch.com/downloads/sphinx-0.9.8.1.tar.gz >> sphinx.log
|
20
36
|
tar xzvf sphinx-0.9.8.1.tar.gz
|
21
37
|
cd sphinx-0.9.8.1
|
22
38
|
./configure #{args.join(" ")}
|
23
39
|
make
|
24
|
-
|
40
|
+
#{try_sudo} make install
|
25
41
|
rm -rf sphinx-0.9.8.1 sphinx-0.9.8.1.tar.gz
|
26
42
|
CMD
|
27
43
|
run commands.split(/\n\s+/).join(" && ")
|
@@ -29,7 +45,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
29
45
|
|
30
46
|
desc "Install Thinking Sphinx as a gem from GitHub"
|
31
47
|
task :ts do
|
32
|
-
|
48
|
+
run "#{try_sudo} gem install freelancing-god-thinking-sphinx --source http://gems.github.com"
|
33
49
|
end
|
34
50
|
end
|
35
51
|
|
@@ -70,12 +86,13 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
70
86
|
|
71
87
|
desc "Add the shared folder for sphinx files for the production environment"
|
72
88
|
task :shared_sphinx_folder, :roles => :web do
|
73
|
-
|
89
|
+
run "mkdir -p #{shared_path}/db/sphinx/production"
|
74
90
|
end
|
75
91
|
|
76
92
|
def rake(*tasks)
|
93
|
+
rails_env = fetch(:rails_env, "production")
|
77
94
|
tasks.each do |t|
|
78
|
-
run "cd #{current_path} && rake #{t} RAILS_ENV
|
95
|
+
run "cd #{current_path} && rake #{t} RAILS_ENV=#{rails_env}"
|
79
96
|
end
|
80
97
|
end
|
81
98
|
end
|