DrMark-thinking-sphinx 1.1.15 → 1.2.5
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 +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
|