thinking-sphinx 2.0.6 → 2.0.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.
Files changed (50) hide show
  1. data/HISTORY +157 -0
  2. data/lib/cucumber/thinking_sphinx/external_world.rb +12 -0
  3. data/lib/cucumber/thinking_sphinx/internal_world.rb +127 -0
  4. data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
  5. data/lib/thinking-sphinx.rb +1 -0
  6. data/lib/thinking_sphinx/action_controller.rb +31 -0
  7. data/lib/thinking_sphinx/active_record/attribute_updates.rb +53 -0
  8. data/lib/thinking_sphinx/active_record/collection_proxy.rb +40 -0
  9. data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +27 -0
  10. data/lib/thinking_sphinx/active_record/delta.rb +65 -0
  11. data/lib/thinking_sphinx/active_record/has_many_association.rb +37 -0
  12. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +21 -0
  13. data/lib/thinking_sphinx/active_record/log_subscriber.rb +61 -0
  14. data/lib/thinking_sphinx/active_record/scopes.rb +110 -0
  15. data/lib/thinking_sphinx/active_record.rb +383 -0
  16. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +87 -0
  17. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +62 -0
  18. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +171 -0
  19. data/lib/thinking_sphinx/association.rb +229 -0
  20. data/lib/thinking_sphinx/attribute.rb +407 -0
  21. data/lib/thinking_sphinx/auto_version.rb +38 -0
  22. data/lib/thinking_sphinx/bundled_search.rb +44 -0
  23. data/lib/thinking_sphinx/class_facet.rb +20 -0
  24. data/lib/thinking_sphinx/configuration.rb +335 -0
  25. data/lib/thinking_sphinx/context.rb +77 -0
  26. data/lib/thinking_sphinx/core/string.rb +15 -0
  27. data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
  28. data/lib/thinking_sphinx/deltas.rb +28 -0
  29. data/lib/thinking_sphinx/deploy/capistrano.rb +99 -0
  30. data/lib/thinking_sphinx/excerpter.rb +23 -0
  31. data/lib/thinking_sphinx/facet.rb +128 -0
  32. data/lib/thinking_sphinx/facet_search.rb +170 -0
  33. data/lib/thinking_sphinx/field.rb +98 -0
  34. data/lib/thinking_sphinx/index/builder.rb +312 -0
  35. data/lib/thinking_sphinx/index/faux_column.rb +118 -0
  36. data/lib/thinking_sphinx/index.rb +157 -0
  37. data/lib/thinking_sphinx/join.rb +37 -0
  38. data/lib/thinking_sphinx/property.rb +185 -0
  39. data/lib/thinking_sphinx/railtie.rb +46 -0
  40. data/lib/thinking_sphinx/search.rb +995 -0
  41. data/lib/thinking_sphinx/search_methods.rb +439 -0
  42. data/lib/thinking_sphinx/sinatra.rb +7 -0
  43. data/lib/thinking_sphinx/source/internal_properties.rb +51 -0
  44. data/lib/thinking_sphinx/source/sql.rb +157 -0
  45. data/lib/thinking_sphinx/source.rb +194 -0
  46. data/lib/thinking_sphinx/tasks.rb +132 -0
  47. data/lib/thinking_sphinx/test.rb +55 -0
  48. data/lib/thinking_sphinx/version.rb +3 -0
  49. data/lib/thinking_sphinx.rb +296 -0
  50. metadata +53 -4
@@ -0,0 +1,439 @@
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
+ # == Filtering by custom attributes
324
+ #
325
+ # Do note that this applies only to sphinx 0.9.9
326
+ #
327
+ # Should you find yourself in desperate need of a filter that involves
328
+ # selecting either one of multiple conditions, one solution could be
329
+ # provided by the :sphinx_select option within the search.
330
+ # This handles which fields are selected by sphinx from its store.
331
+ #
332
+ # The default value is "*", and you can add custom fields using syntax
333
+ # similar to sql:
334
+ #
335
+ # Flower.search "foo",
336
+ # :sphinx_select => "*, petals < 1 or color = 2 as grass"
337
+ #
338
+ # This will add the 'grass' attribute, which will now be usable in your
339
+ # filters.
340
+ #
341
+ # == Handling a Stale Index
342
+ #
343
+ # Especially if you don't use delta indexing, you risk having records in
344
+ # the Sphinx index that are no longer in the database. By default, those
345
+ # will simply come back as nils:
346
+ #
347
+ # >> pat_user.delete
348
+ # >> User.search("pat")
349
+ # Sphinx Result: [1,2]
350
+ # => [nil, <#User id: 2>]
351
+ #
352
+ # (If you search across multiple models, you'll get
353
+ # ActiveRecord::RecordNotFound.)
354
+ #
355
+ # You can simply Array#compact these results or handle the nils in some
356
+ # other way, but Sphinx will still report two results, and the missing
357
+ # records may upset your layout.
358
+ #
359
+ # If you pass :retry_stale => true to a single-model search, missing
360
+ # records will cause Thinking Sphinx to retry the query but excluding
361
+ # those records. Since search is paginated, the new search could
362
+ # potentially include missing records as well, so by default Thinking
363
+ # Sphinx will retry three times. Pass :retry_stale => 5 to retry five
364
+ # times, and so on. If there are still missing ids on the last retry, they
365
+ # are shown as nils.
366
+ #
367
+ def search(*args)
368
+ ThinkingSphinx::Search.new *search_options(args)
369
+ end
370
+
371
+ # Searches for results that match the parameters provided. Will only
372
+ # return the ids for the matching objects. See #search for syntax
373
+ # examples.
374
+ #
375
+ # Note that this only searches the Sphinx index, with no ActiveRecord
376
+ # queries. Thus, if your index is not in sync with the database, this
377
+ # method may return ids that no longer exist there.
378
+ #
379
+ def search_for_ids(*args)
380
+ ThinkingSphinx::Search.new *search_options(args, :ids_only => true)
381
+ end
382
+
383
+ # Checks if a document with the given id exists within a specific index.
384
+ # Expected parameters:
385
+ #
386
+ # - ID of the document
387
+ # - Index to check within
388
+ # - Options hash (defaults to {})
389
+ #
390
+ # Example:
391
+ #
392
+ # ThinkingSphinx.search_for_id(10, "user_core", :class => User)
393
+ #
394
+ def search_for_id(id, index, options = {})
395
+ ThinkingSphinx::Search.new(
396
+ *search_options([],
397
+ :ids_only => true,
398
+ :index => index,
399
+ :id_range => id..id
400
+ )
401
+ ).any?
402
+ end
403
+
404
+ def count(*args)
405
+ search_context ? super : search_count(*args)
406
+ end
407
+
408
+ def search_count(*args)
409
+ search = ThinkingSphinx::Search.new(
410
+ *search_options(args, :ids_only => true)
411
+ )
412
+ search.first # forces the query
413
+ search.total_entries
414
+ end
415
+
416
+ # Model.facets *args
417
+ # ThinkingSphinx.facets *args
418
+ # ThinkingSphinx.facets *args, :all_facets => true
419
+ # ThinkingSphinx.facets *args, :class_facet => false
420
+ #
421
+ def facets(*args)
422
+ ThinkingSphinx::FacetSearch.new *search_options(args)
423
+ end
424
+
425
+ private
426
+
427
+ def search_options(args, options = {})
428
+ options = args.extract_options!.merge(options)
429
+ options[:classes] ||= classes_option
430
+ args << options
431
+ end
432
+
433
+ def classes_option
434
+ classes_option = [search_context].compact
435
+ classes_option.empty? ? nil : classes_option
436
+ end
437
+ end
438
+ end
439
+ end
@@ -0,0 +1,7 @@
1
+ require 'thinking_sphinx'
2
+
3
+ ThinkingSphinx::Configuration.instance
4
+
5
+ ActiveSupport.on_load :active_record do
6
+ include ThinkingSphinx::ActiveRecord
7
+ end
@@ -0,0 +1,51 @@
1
+ module ThinkingSphinx
2
+ class Source
3
+ module InternalProperties
4
+ def add_internal_attributes_and_facets
5
+ add_internal_attribute :sphinx_internal_id, nil,
6
+ @model.primary_key_for_sphinx.to_sym
7
+ add_internal_attribute :sphinx_deleted, :integer, "0"
8
+ add_internal_attribute :class_crc, :integer, crc_column, true
9
+
10
+ unless Riddle.loaded_version.to_i < 2
11
+ add_internal_attribute :sphinx_internal_class, :string, internal_class_column, true
12
+ add_internal_facet :sphinx_internal_class
13
+ else
14
+ add_internal_facet :class_crc
15
+ end
16
+ end
17
+
18
+ def add_internal_attribute(name, type, contents, facet = false)
19
+ return unless attribute_by_alias(name).nil?
20
+
21
+ Attribute.new(self,
22
+ ThinkingSphinx::Index::FauxColumn.new(contents),
23
+ :type => type,
24
+ :as => name,
25
+ :facet => facet,
26
+ :admin => true
27
+ )
28
+ end
29
+
30
+ def add_internal_facet(name)
31
+ return unless facet_by_alias(name).nil?
32
+
33
+ @model.sphinx_facets << ClassFacet.new(attribute_by_alias(name))
34
+ end
35
+
36
+ def attribute_by_alias(attr_alias)
37
+ @attributes.detect { |attrib| attrib.alias == attr_alias }
38
+ end
39
+
40
+ def facet_by_alias(name)
41
+ @model.sphinx_facets.detect { |facet| facet.name == name }
42
+ end
43
+
44
+ def subclasses_to_s
45
+ "'" + (@model.send(:descendants).collect { |klass|
46
+ klass.to_crc32.to_s
47
+ } << @model.to_crc32.to_s).join(",") + "'"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,157 @@
1
+ module ThinkingSphinx
2
+ class Source
3
+ module SQL
4
+ # Generates the big SQL statement to get the data back for all the fields
5
+ # and attributes, using all the relevant association joins. If you want
6
+ # the version filtered for delta values, send through :delta => true in
7
+ # the options. Won't do much though if the index isn't set up to support a
8
+ # delta sibling.
9
+ #
10
+ # Examples:
11
+ #
12
+ # source.to_sql
13
+ # source.to_sql(:delta => true)
14
+ #
15
+ def to_sql(options={})
16
+ relation = @model.unscoped
17
+ pre_select = 'SQL_NO_CACHE ' if adapter.sphinx_identifier == "mysql"
18
+ relation = relation.select(
19
+ pre_select.to_s + sql_select_clause(options[:offset])
20
+ )
21
+
22
+ all_associations.each do |assoc|
23
+ relation = relation.joins(assoc.arel_join)
24
+ end
25
+
26
+ relation = relation.where(sql_where_clause(options))
27
+ relation = relation.group(sql_group_clause)
28
+ relation = relation.order('NULL') if adapter.sphinx_identifier == "mysql"
29
+ relation.to_sql
30
+ end
31
+
32
+ # Simple helper method for the query range SQL - which is a statement that
33
+ # returns minimum and maximum id values. These can be filtered by delta -
34
+ # so pass in :delta => true to get the delta version of the SQL.
35
+ #
36
+ def to_sql_query_range(options={})
37
+ return nil if @index.options[:disable_range]
38
+
39
+ min_statement = adapter.convert_nulls(
40
+ "MIN(#{quote_column(@model.primary_key_for_sphinx)})", 1
41
+ )
42
+ max_statement = adapter.convert_nulls(
43
+ "MAX(#{quote_column(@model.primary_key_for_sphinx)})", 1
44
+ )
45
+
46
+ sql = "SELECT #{min_statement}, #{max_statement} " +
47
+ "FROM #{@model.quoted_table_name} "
48
+ if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
49
+ sql << "WHERE #{@index.delta_object.clause(@model, options[:delta])}"
50
+ end
51
+
52
+ sql
53
+ end
54
+
55
+ # Simple helper method for the query info SQL - which is a statement that
56
+ # returns the single row for a corresponding id.
57
+ #
58
+ def to_sql_query_info(offset)
59
+ "SELECT * FROM #{@model.quoted_table_name} WHERE " +
60
+ "#{quote_column(@model.primary_key_for_sphinx)} = (($id - #{offset}) / #{ThinkingSphinx.context.indexed_models.size})"
61
+ end
62
+
63
+ def sql_select_clause(offset)
64
+ unique_id_expr = ThinkingSphinx.unique_id_expression(adapter, offset)
65
+
66
+ (
67
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} #{unique_id_expr} AS #{quote_column(@model.primary_key_for_sphinx)} "] +
68
+ @fields.collect { |field| field.to_select_sql } +
69
+ @attributes.collect { |attribute| attribute.to_select_sql }
70
+ ).compact.join(", ")
71
+ end
72
+
73
+ def sql_where_clause(options)
74
+ logic = []
75
+ logic += [
76
+ "#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} >= $start",
77
+ "#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} <= $end"
78
+ ] unless @index.options[:disable_range]
79
+
80
+ if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
81
+ logic << "#{@index.delta_object.clause(@model, options[:delta])}"
82
+ end
83
+
84
+ logic += (@conditions || [])
85
+ logic.join(' AND ')
86
+ end
87
+
88
+ def sql_group_clause
89
+ internal_groupings = []
90
+ if @model.column_names.include?(@model.inheritance_column)
91
+ internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
92
+ end
93
+
94
+ (
95
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)}"] +
96
+ @fields.collect { |field| field.to_group_sql }.compact +
97
+ @attributes.collect { |attribute| attribute.to_group_sql }.compact +
98
+ @groupings + internal_groupings
99
+ ).join(", ")
100
+ end
101
+
102
+ def sql_query_pre_for_core
103
+ if self.delta? && !@index.delta_object.reset_query(@model).blank?
104
+ [@index.delta_object.reset_query(@model)]
105
+ else
106
+ []
107
+ end
108
+ end
109
+
110
+ def sql_query_pre_for_delta
111
+ [""]
112
+ end
113
+
114
+ def quote_column(column)
115
+ @model.connection.quote_column_name(column)
116
+ end
117
+
118
+ def crc_column
119
+ if @model.table_exists? &&
120
+ @model.column_names.include?(@model.inheritance_column)
121
+
122
+ types = types_to_crcs
123
+ return @model.to_crc32.to_s if types.empty?
124
+
125
+ adapter.case(adapter.convert_nulls(
126
+ adapter.quote_with_table(@model.inheritance_column)),
127
+ types, @model.to_crc32)
128
+ else
129
+ @model.to_crc32.to_s
130
+ end
131
+ end
132
+
133
+ def internal_class_column
134
+ if @model.table_exists? &&
135
+ @model.column_names.include?(@model.inheritance_column)
136
+ adapter.quote_with_table(@model.inheritance_column)
137
+ else
138
+ "'#{@model.name}'"
139
+ end
140
+ end
141
+
142
+ def type_values
143
+ @model.connection.select_values <<-SQL
144
+ SELECT DISTINCT #{@model.inheritance_column}
145
+ FROM #{@model.table_name}
146
+ SQL
147
+ end
148
+
149
+ def types_to_crcs
150
+ type_values.compact.inject({}) { |hash, type|
151
+ hash[type] = type.to_crc32
152
+ hash
153
+ }
154
+ end
155
+ end
156
+ end
157
+ end