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.
Files changed (63) hide show
  1. data/README.textile +22 -0
  2. data/VERSION.yml +4 -0
  3. data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
  4. data/lib/thinking_sphinx/active_record.rb +27 -7
  5. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +9 -3
  6. data/lib/thinking_sphinx/association.rb +4 -1
  7. data/lib/thinking_sphinx/attribute.rb +91 -30
  8. data/lib/thinking_sphinx/configuration.rb +51 -12
  9. data/lib/thinking_sphinx/deltas/datetime_delta.rb +2 -2
  10. data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
  11. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
  12. data/lib/thinking_sphinx/deltas/delayed_delta.rb +3 -0
  13. data/lib/thinking_sphinx/deploy/capistrano.rb +25 -8
  14. data/lib/thinking_sphinx/excerpter.rb +22 -0
  15. data/lib/thinking_sphinx/facet.rb +1 -1
  16. data/lib/thinking_sphinx/facet_search.rb +134 -0
  17. data/lib/thinking_sphinx/index.rb +2 -1
  18. data/lib/thinking_sphinx/rails_additions.rb +14 -0
  19. data/lib/thinking_sphinx/search.rb +599 -658
  20. data/lib/thinking_sphinx/search_methods.rb +421 -0
  21. data/lib/thinking_sphinx/source/internal_properties.rb +1 -1
  22. data/lib/thinking_sphinx/source/sql.rb +17 -13
  23. data/lib/thinking_sphinx/source.rb +6 -6
  24. data/lib/thinking_sphinx/tasks.rb +42 -8
  25. data/lib/thinking_sphinx.rb +82 -54
  26. data/rails/init.rb +14 -0
  27. data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +5 -5
  28. data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
  29. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
  30. data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +51 -31
  31. data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +4 -5
  32. data/spec/lib/thinking_sphinx/attribute_spec.rb +465 -0
  33. data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +161 -29
  34. data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
  35. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  36. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  37. data/spec/{unit → lib}/thinking_sphinx/facet_spec.rb +24 -0
  38. data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +8 -8
  39. data/spec/{unit → lib}/thinking_sphinx/index/builder_spec.rb +6 -2
  40. data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
  41. data/spec/lib/thinking_sphinx/index_spec.rb +45 -0
  42. data/spec/{unit → lib}/thinking_sphinx/rails_additions_spec.rb +25 -5
  43. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  44. data/spec/lib/thinking_sphinx/search_spec.rb +960 -0
  45. data/spec/{unit → lib}/thinking_sphinx/source_spec.rb +63 -2
  46. data/spec/{unit → lib}/thinking_sphinx_spec.rb +32 -4
  47. data/tasks/distribution.rb +36 -35
  48. data/vendor/riddle/lib/riddle/client/message.rb +4 -3
  49. data/vendor/riddle/lib/riddle/client.rb +3 -0
  50. data/vendor/riddle/lib/riddle/configuration/section.rb +8 -2
  51. data/vendor/riddle/lib/riddle/controller.rb +17 -7
  52. data/vendor/riddle/lib/riddle.rb +1 -1
  53. metadata +79 -83
  54. data/lib/thinking_sphinx/active_record/search.rb +0 -57
  55. data/lib/thinking_sphinx/collection.rb +0 -148
  56. data/lib/thinking_sphinx/facet_collection.rb +0 -59
  57. data/lib/thinking_sphinx/search/facets.rb +0 -98
  58. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
  59. data/spec/unit/thinking_sphinx/attribute_spec.rb +0 -232
  60. data/spec/unit/thinking_sphinx/collection_spec.rb +0 -14
  61. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +0 -64
  62. data/spec/unit/thinking_sphinx/index_spec.rb +0 -139
  63. data/spec/unit/thinking_sphinx/search_spec.rb +0 -130
@@ -0,0 +1,421 @@
1
+ module ThinkingSphinx
2
+ module SearchMethods
3
+ def self.included(base)
4
+ base.class_eval do
5
+ extend ThinkingSphinx::SearchMethods::ClassMethods
6
+ end
7
+ end
8
+
9
+ module ClassMethods
10
+ def search_context
11
+ # Comparing to name string to avoid class inheritance complications
12
+ case self.class.name
13
+ when 'Class'
14
+ self
15
+ else
16
+ nil
17
+ end
18
+ end
19
+
20
+ # Searches through the Sphinx indexes for relevant matches. There's
21
+ # various ways to search, sort, group and filter - which are covered
22
+ # below.
23
+ #
24
+ # Also, if you have WillPaginate installed, the search method can be used
25
+ # just like paginate. The same parameters - :page and :per_page - work as
26
+ # expected, and the returned result set can be used by the will_paginate
27
+ # helper.
28
+ #
29
+ # == Basic Searching
30
+ #
31
+ # The simplest way of searching is straight text.
32
+ #
33
+ # ThinkingSphinx.search "pat"
34
+ # ThinkingSphinx.search "google"
35
+ # User.search "pat", :page => (params[:page] || 1)
36
+ # Article.search "relevant news issue of the day"
37
+ #
38
+ # If you specify :include, like in an #find call, this will be respected
39
+ # when loading the relevant models from the search results.
40
+ #
41
+ # User.search "pat", :include => :posts
42
+ #
43
+ # == Match Modes
44
+ #
45
+ # Sphinx supports 5 different matching modes. By default Thinking Sphinx
46
+ # uses :all, which unsurprisingly requires all the supplied search terms
47
+ # to match a result.
48
+ #
49
+ # Alternative modes include:
50
+ #
51
+ # User.search "pat allan", :match_mode => :any
52
+ # User.search "pat allan", :match_mode => :phrase
53
+ # User.search "pat | allan", :match_mode => :boolean
54
+ # User.search "@name pat | @username pat", :match_mode => :extended
55
+ #
56
+ # Any will find results with any of the search terms. Phrase treats the
57
+ # search terms a single phrase instead of individual words. Boolean and
58
+ # extended allow for more complex query syntax, refer to the sphinx
59
+ # documentation for further details.
60
+ #
61
+ # == Weighting
62
+ #
63
+ # Sphinx has support for weighting, where matches in one field can be
64
+ # considered more important than in another. Weights are integers, with 1
65
+ # as the default. They can be set per-search like this:
66
+ #
67
+ # User.search "pat allan", :field_weights => { :alias => 4, :aka => 2 }
68
+ #
69
+ # If you're searching multiple models, you can set per-index weights:
70
+ #
71
+ # ThinkingSphinx.search "pat", :index_weights => { User => 10 }
72
+ #
73
+ # See http://sphinxsearch.com/doc.html#weighting for further details.
74
+ #
75
+ # == Searching by Fields
76
+ #
77
+ # If you want to step it up a level, you can limit your search terms to
78
+ # specific fields:
79
+ #
80
+ # User.search :conditions => {:name => "pat"}
81
+ #
82
+ # This uses Sphinx's extended match mode, unless you specify a different
83
+ # match mode explicitly (but then this way of searching won't work). Also
84
+ # note that you don't need to put in a search string.
85
+ #
86
+ # == Searching by Attributes
87
+ #
88
+ # Also known as filters, you can limit your searches to documents that
89
+ # have specific values for their attributes. There are three ways to do
90
+ # this. The first two techniques work in all scenarios - using the :with
91
+ # or :with_all options.
92
+ #
93
+ # ThinkingSphinx.search :with => {:tag_ids => 10}
94
+ # ThinkingSphinx.search :with => {:tag_ids => [10,12]}
95
+ # ThinkingSphinx.search :with_all => {:tag_ids => [10,12]}
96
+ #
97
+ # The first :with search will match records with a tag_id attribute of 10.
98
+ # The second :with will match records with a tag_id attribute of 10 OR 12.
99
+ # If you need to find records that are tagged with ids 10 AND 12, you
100
+ # will need to use the :with_all search parameter. This is particuarly
101
+ # useful in conjunction with Multi Value Attributes (MVAs).
102
+ #
103
+ # The third filtering technique is only viable if you're searching with a
104
+ # specific model (not multi-model searching). With a single model,
105
+ # Thinking Sphinx can figure out what attributes and fields are available,
106
+ # so you can put it all in the :conditions hash, and it will sort it out.
107
+ #
108
+ # Node.search :conditions => {:parent_id => 10}
109
+ #
110
+ # Filters can be single values, arrays of values, or ranges.
111
+ #
112
+ # Article.search "East Timor", :conditions => {:rating => 3..5}
113
+ #
114
+ # == Excluding by Attributes
115
+ #
116
+ # Sphinx also supports negative filtering - where the filters are of
117
+ # attribute values to exclude. This is done with the :without option:
118
+ #
119
+ # User.search :without => {:role_id => 1}
120
+ #
121
+ # == Excluding by Primary Key
122
+ #
123
+ # There is a shortcut to exclude records by their ActiveRecord primary
124
+ # key:
125
+ #
126
+ # User.search :without_ids => 1
127
+ #
128
+ # Pass an array or a single value.
129
+ #
130
+ # The primary key must be an integer as a negative filter is used. Note
131
+ # that for multi-model search, an id may occur in more than one model.
132
+ #
133
+ # == Infix (Star) Searching
134
+ #
135
+ # Enable infix searching by something like this in config/sphinx.yml:
136
+ #
137
+ # development:
138
+ # enable_star: 1
139
+ # min_infix_len: 2
140
+ #
141
+ # Note that this will make indexing take longer.
142
+ #
143
+ # With those settings (and after reindexing), wildcard asterisks can be
144
+ # used in queries:
145
+ #
146
+ # Location.search "*elbourn*"
147
+ #
148
+ # To automatically add asterisks around every token (but not operators),
149
+ # pass the :star option:
150
+ #
151
+ # Location.search "elbourn -ustrali", :star => true,
152
+ # :match_mode => :boolean
153
+ #
154
+ # This would become "*elbourn* -*ustrali*". The :star option only adds the
155
+ # asterisks. You need to make the config/sphinx.yml changes yourself.
156
+ #
157
+ # By default, the tokens are assumed to match the regular expression
158
+ # /\w+/u. If you've modified the charset_table, pass another regular
159
+ # expression, e.g.
160
+ #
161
+ # User.search("oo@bar.c", :star => /[\w@.]+/u)
162
+ #
163
+ # to search for "*oo@bar.c*" and not "*oo*@*bar*.*c*".
164
+ #
165
+ # == Sorting
166
+ #
167
+ # Sphinx can only sort by attributes, so generally you will need to avoid
168
+ # using field names in your :order option. However, if you're searching
169
+ # on a single model, and have specified some fields as sortable, you can
170
+ # use those field names and Thinking Sphinx will interpret accordingly.
171
+ # Remember: this will only happen for single-model searches, and only
172
+ # through the :order option.
173
+ #
174
+ # Location.search "Melbourne", :order => :state
175
+ # User.search :conditions => {:role_id => 2}, :order => "name ASC"
176
+ #
177
+ # Keep in mind that if you use a string, you *must* specify the direction
178
+ # (ASC or DESC) else Sphinx won't return any results. If you use a symbol
179
+ # then Thinking Sphinx assumes ASC, but if you wish to state otherwise,
180
+ # use the :sort_mode option:
181
+ #
182
+ # Location.search "Melbourne", :order => :state, :sort_mode => :desc
183
+ #
184
+ # Of course, there are other sort modes - check out the Sphinx
185
+ # documentation[http://sphinxsearch.com/doc.html] for that level of
186
+ # detail though.
187
+ #
188
+ # If desired, you can sort by a column in your model instead of a sphinx
189
+ # field or attribute. This sort only applies to the current page, so is
190
+ # most useful when performing a search with a single page of results.
191
+ #
192
+ # User.search("pat", :sql_order => "name")
193
+ #
194
+ # == Grouping
195
+ #
196
+ # For this you can use the group_by, group_clause and group_function
197
+ # options - which are all directly linked to Sphinx's expectations. No
198
+ # magic from Thinking Sphinx. It can get a little tricky, so make sure
199
+ # you read all the relevant
200
+ # documentation[http://sphinxsearch.com/doc.html#clustering] first.
201
+ #
202
+ # Grouping is done via three parameters within the options hash
203
+ # * <tt>:group_function</tt> determines the way grouping is done
204
+ # * <tt>:group_by</tt> determines the field which is used for grouping
205
+ # * <tt>:group_clause</tt> determines the sorting order
206
+ #
207
+ # As a convenience, you can also use
208
+ # * <tt>:group</tt>
209
+ # which sets :group_by and defaults to :group_function of :attr
210
+ #
211
+ # === group_function
212
+ #
213
+ # Valid values for :group_function are
214
+ # * <tt>:day</tt>, <tt>:week</tt>, <tt>:month</tt>, <tt>:year</tt> - Grouping is done by the respective timeframes.
215
+ # * <tt>:attr</tt>, <tt>:attrpair</tt> - Grouping is done by the specified attributes(s)
216
+ #
217
+ # === group_by
218
+ #
219
+ # This parameter denotes the field by which grouping is done. Note that
220
+ # the specified field must be a sphinx attribute or index.
221
+ #
222
+ # === group_clause
223
+ #
224
+ # This determines the sorting order of the groups. In a grouping search,
225
+ # the matches within a group will sorted by the <tt>:sort_mode</tt> and
226
+ # <tt>:order</tt> parameters. The group matches themselves however, will
227
+ # be sorted by <tt>:group_clause</tt>.
228
+ #
229
+ # The syntax for this is the same as an order parameter in extended sort
230
+ # mode. Namely, you can specify an SQL-like sort expression with up to 5
231
+ # attributes (including internal attributes), eg: "@relevance DESC, price
232
+ # ASC, @id DESC"
233
+ #
234
+ # === Grouping by timestamp
235
+ #
236
+ # Timestamp grouping groups off items by the day, week, month or year of
237
+ # the attribute given. In order to do this you need to define a timestamp
238
+ # attribute, which pretty much looks like the standard defintion for any
239
+ # attribute.
240
+ #
241
+ # define_index do
242
+ # #
243
+ # # All your other stuff
244
+ # #
245
+ # has :created_at
246
+ # end
247
+ #
248
+ # When you need to fire off your search, it'll go something to the tune of
249
+ #
250
+ # Fruit.search "apricot", :group_function => :day,
251
+ # :group_by => 'created_at'
252
+ #
253
+ # The <tt>@groupby</tt> special attribute will contain the date for that
254
+ # group. Depending on the <tt>:group_function</tt> parameter, the date
255
+ # format will be:
256
+ #
257
+ # * <tt>:day</tt> - YYYYMMDD
258
+ # * <tt>:week</tt> - YYYYNNN (NNN is the first day of the week in question,
259
+ # counting from the start of the year )
260
+ # * <tt>:month</tt> - YYYYMM
261
+ # * <tt>:year</tt> - YYYY
262
+ #
263
+ # === Grouping by attribute
264
+ #
265
+ # The syntax is the same as grouping by timestamp, except for the fact
266
+ # that the <tt>:group_function</tt> parameter is changed.
267
+ #
268
+ # Fruit.search "apricot", :group_function => :attr, :group_by => 'size'
269
+ #
270
+ # == Geo/Location Searching
271
+ #
272
+ # Sphinx - and therefore Thinking Sphinx - has the facility to search
273
+ # around a geographical point, using a given latitude and longitude. To
274
+ # take advantage of this, you will need to have both of those values in
275
+ # attributes. To search with that point, you can then use one of the
276
+ # following syntax examples:
277
+ #
278
+ # Address.search "Melbourne", :geo => [1.4, -2.217],
279
+ # :order => "@geodist asc"
280
+ # Address.search "Australia", :geo => [-0.55, 3.108],
281
+ # :order => "@geodist asc" :latitude_attr => "latit",
282
+ # :longitude_attr => "longit"
283
+ #
284
+ # The first example applies when your latitude and longitude attributes
285
+ # are named any of lat, latitude, lon, long or longitude. If that's not
286
+ # the case, you will need to explicitly state them in your search, _or_
287
+ # you can do so in your model:
288
+ #
289
+ # define_index do
290
+ # has :latit # Float column, stored in radians
291
+ # has :longit # Float column, stored in radians
292
+ #
293
+ # set_property :latitude_attr => "latit"
294
+ # set_property :longitude_attr => "longit"
295
+ # end
296
+ #
297
+ # Now, geo-location searching really only has an affect if you have a
298
+ # filter, sort or grouping clause related to it - otherwise it's just a
299
+ # normal search, and _will not_ return a distance value otherwise. To
300
+ # make use of the positioning difference, use the special attribute
301
+ # "@geodist" in any of your filters or sorting or grouping clauses.
302
+ #
303
+ # And don't forget - both the latitude and longitude you use in your
304
+ # search, and the values in your indexes, need to be stored as a float in
305
+ # radians, _not_ degrees. Keep in mind that if you do this conversion in
306
+ # SQL you will need to explicitly declare a column type of :float.
307
+ #
308
+ # define_index do
309
+ # has 'RADIANS(lat)', :as => :lat, :type => :float
310
+ # # ...
311
+ # end
312
+ #
313
+ # Once you've got your results set, you can access the distances as
314
+ # follows:
315
+ #
316
+ # @results.each_with_geodist do |result, distance|
317
+ # # ...
318
+ # end
319
+ #
320
+ # The distance value is returned as a float, representing the distance in
321
+ # metres.
322
+ #
323
+ # == Handling a Stale Index
324
+ #
325
+ # Especially if you don't use delta indexing, you risk having records in
326
+ # the Sphinx index that are no longer in the database. By default, those
327
+ # will simply come back as nils:
328
+ #
329
+ # >> pat_user.delete
330
+ # >> User.search("pat")
331
+ # Sphinx Result: [1,2]
332
+ # => [nil, <#User id: 2>]
333
+ #
334
+ # (If you search across multiple models, you'll get
335
+ # ActiveRecord::RecordNotFound.)
336
+ #
337
+ # You can simply Array#compact these results or handle the nils in some
338
+ # other way, but Sphinx will still report two results, and the missing
339
+ # records may upset your layout.
340
+ #
341
+ # If you pass :retry_stale => true to a single-model search, missing
342
+ # records will cause Thinking Sphinx to retry the query but excluding
343
+ # those records. Since search is paginated, the new search could
344
+ # potentially include missing records as well, so by default Thinking
345
+ # Sphinx will retry three times. Pass :retry_stale => 5 to retry five
346
+ # times, and so on. If there are still missing ids on the last retry, they
347
+ # are shown as nils.
348
+ #
349
+ def search(*args)
350
+ ThinkingSphinx::Search.new *search_options(args)
351
+ end
352
+
353
+ # Searches for results that match the parameters provided. Will only
354
+ # return the ids for the matching objects. See #search for syntax
355
+ # examples.
356
+ #
357
+ # Note that this only searches the Sphinx index, with no ActiveRecord
358
+ # queries. Thus, if your index is not in sync with the database, this
359
+ # method may return ids that no longer exist there.
360
+ #
361
+ def search_for_ids(*args)
362
+ ThinkingSphinx::Search.new *search_options(args, :ids_only => true)
363
+ end
364
+
365
+ # Checks if a document with the given id exists within a specific index.
366
+ # Expected parameters:
367
+ #
368
+ # - ID of the document
369
+ # - Index to check within
370
+ # - Options hash (defaults to {})
371
+ #
372
+ # Example:
373
+ #
374
+ # ThinkingSphinx.search_for_id(10, "user_core", :class => User)
375
+ #
376
+ def search_for_id(id, index, options = {})
377
+ ThinkingSphinx::Search.new(
378
+ *search_options([],
379
+ :ids_only => true,
380
+ :index => index,
381
+ :id_range => id..id
382
+ )
383
+ ).any?
384
+ end
385
+
386
+ def count(*args)
387
+ search_context ? super : search_count(*args)
388
+ end
389
+
390
+ def search_count(*args)
391
+ search = ThinkingSphinx::Search.new(
392
+ *search_options(args, :ids_only => true)
393
+ )
394
+ search.first # forces the query
395
+ search.total_entries
396
+ end
397
+
398
+ # Model.facets *args
399
+ # ThinkingSphinx.facets *args
400
+ # ThinkingSphinx.facets *args, :all_facets => true
401
+ # ThinkingSphinx.facets *args, :class_facet => false
402
+ #
403
+ def facets(*args)
404
+ ThinkingSphinx::FacetSearch.new *search_options(args)
405
+ end
406
+
407
+ private
408
+
409
+ def search_options(args, options = {})
410
+ options = args.extract_options!.merge(options)
411
+ options[:classes] ||= classes_option
412
+ args << options
413
+ end
414
+
415
+ def classes_option
416
+ classes_option = [search_context].compact
417
+ classes_option.empty? ? nil : classes_option
418
+ end
419
+ end
420
+ end
421
+ end
@@ -2,7 +2,7 @@ module ThinkingSphinx
2
2
  class Source
3
3
  module InternalProperties
4
4
  def add_internal_attributes_and_facets
5
- add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key.to_sym
5
+ add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key_for_sphinx.to_sym
6
6
  add_internal_attribute :class_crc, :integer, crc_column, true
7
7
  add_internal_attribute :subclass_crcs, :multi, subclasses_to_s
8
8
  add_internal_attribute :sphinx_deleted, :integer, "0"
@@ -17,7 +17,7 @@ module ThinkingSphinx
17
17
  SELECT #{ sql_select_clause options[:offset] }
18
18
  FROM #{ @model.quoted_table_name }
19
19
  #{ all_associations.collect { |assoc| assoc.to_sql }.join(' ') }
20
- WHERE #{ sql_where_clause(options) }
20
+ #{ sql_where_clause(options) }
21
21
  GROUP BY #{ sql_group_clause }
22
22
  SQL
23
23
 
@@ -30,11 +30,13 @@ GROUP BY #{ sql_group_clause }
30
30
  # so pass in :delta => true to get the delta version of the SQL.
31
31
  #
32
32
  def to_sql_query_range(options={})
33
+ return nil if @index.options[:disable_range]
34
+
33
35
  min_statement = adapter.convert_nulls(
34
- "MIN(#{quote_column(@model.primary_key)})", 1
36
+ "MIN(#{quote_column(@model.primary_key_for_sphinx)})", 1
35
37
  )
36
38
  max_statement = adapter.convert_nulls(
37
- "MAX(#{quote_column(@model.primary_key)})", 1
39
+ "MAX(#{quote_column(@model.primary_key_for_sphinx)})", 1
38
40
  )
39
41
 
40
42
  sql = "SELECT #{min_statement}, #{max_statement} " +
@@ -51,32 +53,32 @@ GROUP BY #{ sql_group_clause }
51
53
  #
52
54
  def to_sql_query_info(offset)
53
55
  "SELECT * FROM #{@model.quoted_table_name} WHERE " +
54
- "#{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
56
+ "#{quote_column(@model.primary_key_for_sphinx)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
55
57
  end
56
58
 
57
59
  def sql_select_clause(offset)
58
60
  unique_id_expr = ThinkingSphinx.unique_id_expression(offset)
59
61
 
60
62
  (
61
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
63
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} #{unique_id_expr} AS #{quote_column(@model.primary_key_for_sphinx)} "] +
62
64
  @fields.collect { |field| field.to_select_sql } +
63
65
  @attributes.collect { |attribute| attribute.to_select_sql }
64
66
  ).compact.join(", ")
65
67
  end
66
68
 
67
69
  def sql_where_clause(options)
68
- logic = [
69
- "#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start",
70
- "#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end"
71
- ]
70
+ logic = []
71
+ logic += [
72
+ "#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} >= $start",
73
+ "#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} <= $end"
74
+ ] unless @index.options[:disable_range]
72
75
 
73
76
  if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
74
77
  logic << "#{@index.delta_object.clause(@model, options[:delta])}"
75
78
  end
76
79
 
77
80
  logic += (@conditions || [])
78
-
79
- logic.join(" AND ")
81
+ logic.empty? ? "" : "WHERE #{logic.join(' AND ')}"
80
82
  end
81
83
 
82
84
  def sql_group_clause
@@ -86,7 +88,7 @@ GROUP BY #{ sql_group_clause }
86
88
  end
87
89
 
88
90
  (
89
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
91
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)}"] +
90
92
  @fields.collect { |field| field.to_group_sql }.compact +
91
93
  @attributes.collect { |attribute| attribute.to_group_sql }.compact +
92
94
  @groupings + internal_groupings
@@ -110,7 +112,9 @@ GROUP BY #{ sql_group_clause }
110
112
  end
111
113
 
112
114
  def crc_column
113
- if @model.column_names.include?(@model.inheritance_column)
115
+ if @model.table_exists? &&
116
+ @model.column_names.include?(@model.inheritance_column)
117
+
114
118
  adapter.cast_to_unsigned(adapter.convert_nulls(
115
119
  adapter.crc(adapter.quote_with_table(@model.inheritance_column), true),
116
120
  @model.to_crc32
@@ -8,7 +8,7 @@ module ThinkingSphinx
8
8
 
9
9
  attr_accessor :model, :fields, :attributes, :conditions, :groupings,
10
10
  :options
11
- attr_reader :base
11
+ attr_reader :base, :index
12
12
 
13
13
  def initialize(index, options = {})
14
14
  @index = index
@@ -56,7 +56,7 @@ module ThinkingSphinx
56
56
  source.parent = "#{name}_core_#{index}"
57
57
 
58
58
  set_source_database_settings source
59
- set_source_attributes source, offset
59
+ set_source_attributes source, offset, true
60
60
  set_source_sql source, offset, true
61
61
 
62
62
  source
@@ -89,9 +89,9 @@ module ThinkingSphinx
89
89
  source.sql_sock = config[:socket]
90
90
  end
91
91
 
92
- def set_source_attributes(source, offset)
92
+ def set_source_attributes(source, offset, delta = false)
93
93
  attributes.each do |attrib|
94
- source.send(attrib.type_to_config) << attrib.config_value(offset)
94
+ source.send(attrib.type_to_config) << attrib.config_value(offset, delta)
95
95
  end
96
96
  end
97
97
 
@@ -102,8 +102,8 @@ module ThinkingSphinx
102
102
 
103
103
  source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
104
104
 
105
- if @options[:group_concat_max_len]
106
- source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
105
+ if @index.local_options[:group_concat_max_len]
106
+ source.sql_query_pre << "SET SESSION group_concat_max_len = #{@index.local_options[:group_concat_max_len]}"
107
107
  end
108
108
 
109
109
  source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
@@ -2,10 +2,19 @@ require 'fileutils'
2
2
 
3
3
  namespace :thinking_sphinx do
4
4
  task :app_env do
5
- Rake::Task[:environment].invoke if defined?(RAILS_ROOT)
5
+ if defined?(RAILS_ROOT)
6
+ Rake::Task[:environment].invoke
7
+ Rails.configuration.cache_classes = false
8
+ end
9
+
6
10
  Rake::Task[:merb_env].invoke if defined?(Merb)
7
11
  end
8
12
 
13
+ desc "Output the current Thinking Sphinx version"
14
+ task :version => :app_env do
15
+ puts "Thinking Sphinx v" + ThinkingSphinx.version
16
+ end
17
+
9
18
  desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings"
10
19
  task :running_start => :app_env do
11
20
  Rake::Task["thinking_sphinx:stop"].invoke if sphinx_running?
@@ -21,9 +30,7 @@ namespace :thinking_sphinx do
21
30
 
22
31
  Dir["#{config.searchd_file_path}/*.spl"].each { |file| File.delete(file) }
23
32
 
24
- cmd = "#{config.bin_path}searchd --pidfile --config #{config.config_file}"
25
- puts cmd
26
- system cmd
33
+ system! "#{config.bin_path}#{config.searchd_binary_name} --pidfile --config \"#{config.config_file}\""
27
34
 
28
35
  sleep(2)
29
36
 
@@ -39,7 +46,7 @@ namespace :thinking_sphinx do
39
46
  raise RuntimeError, "searchd is not running." unless sphinx_running?
40
47
  config = ThinkingSphinx::Configuration.instance
41
48
  pid = sphinx_pid
42
- system "#{config.bin_path}searchd --stop --config #{config.config_file}"
49
+ system! "#{config.bin_path}#{config.searchd_binary_name} --stop --config \"#{config.config_file}\""
43
50
  puts "Stopped search daemon (pid #{pid})."
44
51
  end
45
52
 
@@ -64,10 +71,17 @@ namespace :thinking_sphinx do
64
71
  end
65
72
 
66
73
  FileUtils.mkdir_p config.searchd_file_path
67
- cmd = "#{config.bin_path}indexer --config #{config.config_file} --all"
74
+ cmd = "#{config.bin_path}#{config.indexer_binary_name} --config \"#{config.config_file}\" --all"
68
75
  cmd << " --rotate" if sphinx_running?
69
- puts cmd
70
- system cmd
76
+
77
+ system! cmd
78
+ end
79
+
80
+ desc "Stop Sphinx (if it's running), rebuild the indexes, and start Sphinx"
81
+ task :rebuild => :app_env do
82
+ Rake::Task["thinking_sphinx:stop"].invoke if sphinx_running?
83
+ Rake::Task["thinking_sphinx:index"].invoke
84
+ Rake::Task["thinking_sphinx:start"].invoke
71
85
  end
72
86
 
73
87
  namespace :index do
@@ -96,6 +110,8 @@ namespace :thinking_sphinx do
96
110
  end
97
111
 
98
112
  namespace :ts do
113
+ desc "Output the current Thinking Sphinx version"
114
+ task :version => "thinking_sphinx:version"
99
115
  desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings"
100
116
  task :run => "thinking_sphinx:running_start"
101
117
  desc "Start a Sphinx searchd daemon using Thinking Sphinx's settings"
@@ -115,6 +131,8 @@ namespace :ts do
115
131
  task :conf => "thinking_sphinx:configure"
116
132
  desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
117
133
  task :config => "thinking_sphinx:configure"
134
+ desc "Stop Sphinx (if it's running), rebuild the indexes, and start Sphinx"
135
+ task :rebuild => "thinking_sphinx:rebuild"
118
136
  desc "Process stored delta index requests"
119
137
  task :dd => "thinking_sphinx:delayed_delta"
120
138
  end
@@ -126,3 +144,19 @@ end
126
144
  def sphinx_running?
127
145
  ThinkingSphinx.sphinx_running?
128
146
  end
147
+
148
+ # a fail-fast, hopefully helpful version of system
149
+ def system!(cmd)
150
+ unless system(cmd)
151
+ raise <<-SYSTEM_CALL_FAILED
152
+ The following command failed:
153
+ #{cmd}
154
+
155
+ This could be caused by a PATH issue in the environment of cron/passenger/etc. Your current PATH:
156
+ #{ENV['PATH']}
157
+ You can set the path to your indexer and searchd binaries using the bin_path property in config/sphinx.yml:
158
+ production:
159
+ bin_path: '/usr/local/bin'
160
+ SYSTEM_CALL_FAILED
161
+ end
162
+ end