DrMark-thinking-sphinx 0.9.9 → 1.1.6

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 (77) hide show
  1. data/README +64 -2
  2. data/lib/thinking_sphinx.rb +88 -11
  3. data/lib/thinking_sphinx/active_record.rb +136 -21
  4. data/lib/thinking_sphinx/active_record/delta.rb +43 -62
  5. data/lib/thinking_sphinx/active_record/has_many_association.rb +1 -1
  6. data/lib/thinking_sphinx/active_record/search.rb +7 -0
  7. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
  8. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
  9. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +130 -0
  10. data/lib/thinking_sphinx/association.rb +17 -0
  11. data/lib/thinking_sphinx/attribute.rb +171 -97
  12. data/lib/thinking_sphinx/collection.rb +126 -2
  13. data/lib/thinking_sphinx/configuration.rb +120 -171
  14. data/lib/thinking_sphinx/core/string.rb +15 -0
  15. data/lib/thinking_sphinx/deltas.rb +27 -0
  16. data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
  17. data/lib/thinking_sphinx/deltas/default_delta.rb +67 -0
  18. data/lib/thinking_sphinx/deltas/delayed_delta.rb +25 -0
  19. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
  20. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
  21. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
  22. data/lib/thinking_sphinx/facet.rb +58 -0
  23. data/lib/thinking_sphinx/facet_collection.rb +60 -0
  24. data/lib/thinking_sphinx/field.rb +18 -52
  25. data/lib/thinking_sphinx/index.rb +246 -199
  26. data/lib/thinking_sphinx/index/builder.rb +85 -16
  27. data/lib/thinking_sphinx/rails_additions.rb +85 -5
  28. data/lib/thinking_sphinx/search.rb +459 -190
  29. data/lib/thinking_sphinx/tasks.rb +128 -0
  30. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +53 -124
  31. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +2 -2
  32. data/spec/unit/thinking_sphinx/active_record_spec.rb +110 -30
  33. data/spec/unit/thinking_sphinx/attribute_spec.rb +16 -149
  34. data/spec/unit/thinking_sphinx/collection_spec.rb +14 -0
  35. data/spec/unit/thinking_sphinx/configuration_spec.rb +54 -412
  36. data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
  37. data/spec/unit/thinking_sphinx/field_spec.rb +0 -79
  38. data/spec/unit/thinking_sphinx/index/builder_spec.rb +1 -29
  39. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +1 -39
  40. data/spec/unit/thinking_sphinx/index_spec.rb +78 -226
  41. data/spec/unit/thinking_sphinx/search_spec.rb +29 -228
  42. data/spec/unit/thinking_sphinx_spec.rb +23 -19
  43. data/tasks/distribution.rb +48 -0
  44. data/tasks/rails.rake +1 -0
  45. data/tasks/testing.rb +86 -0
  46. data/vendor/after_commit/LICENSE +20 -0
  47. data/vendor/after_commit/README +16 -0
  48. data/vendor/after_commit/Rakefile +22 -0
  49. data/vendor/after_commit/init.rb +8 -0
  50. data/vendor/after_commit/lib/after_commit.rb +45 -0
  51. data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
  52. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
  53. data/vendor/after_commit/test/after_commit_test.rb +53 -0
  54. data/vendor/delayed_job/lib/delayed/job.rb +251 -0
  55. data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
  56. data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
  57. data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
  58. data/{lib → vendor/riddle/lib}/riddle.rb +9 -5
  59. data/{lib → vendor/riddle/lib}/riddle/client.rb +6 -26
  60. data/{lib → vendor/riddle/lib}/riddle/client/filter.rb +10 -1
  61. data/{lib → vendor/riddle/lib}/riddle/client/message.rb +0 -0
  62. data/{lib → vendor/riddle/lib}/riddle/client/response.rb +0 -0
  63. data/vendor/riddle/lib/riddle/configuration.rb +33 -0
  64. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
  65. data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
  66. data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
  67. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
  68. data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
  69. data/vendor/riddle/lib/riddle/configuration/section.rb +37 -0
  70. data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
  71. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
  72. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
  73. data/vendor/riddle/lib/riddle/controller.rb +44 -0
  74. metadata +63 -10
  75. data/lib/test.rb +0 -46
  76. data/tasks/thinking_sphinx_tasks.rake +0 -1
  77. data/tasks/thinking_sphinx_tasks.rb +0 -86
@@ -9,7 +9,8 @@ module ThinkingSphinx
9
9
  # Enjoy.
10
10
  #
11
11
  class Index
12
- attr_accessor :model, :fields, :attributes, :conditions, :delta, :options
12
+ attr_accessor :model, :fields, :attributes, :conditions, :groupings,
13
+ :delta_object, :options
13
14
 
14
15
  # Create a new index instance by passing in the model it is tied to, and
15
16
  # a block to build it with (optional but recommended). For documentation
@@ -31,8 +32,9 @@ module ThinkingSphinx
31
32
  @fields = []
32
33
  @attributes = []
33
34
  @conditions = []
35
+ @groupings = []
34
36
  @options = {}
35
- @delta = false
37
+ @delta_object = nil
36
38
 
37
39
  initialize_from_builder(&block) if block_given?
38
40
  end
@@ -45,65 +47,36 @@ module ThinkingSphinx
45
47
  model.name.underscore.tr(':/\\', '_')
46
48
  end
47
49
 
48
- def empty?(part = :core)
49
- config = ThinkingSphinx::Configuration.new
50
- File.size?("#{config.searchd_file_path}/#{self.name}_#{part}.spa").nil?
50
+ def to_riddle_for_core(offset, index)
51
+ add_internal_attributes_and_facets
52
+ link!
53
+
54
+ source = Riddle::Configuration::SQLSource.new(
55
+ "#{name}_core_#{index}", adapter.sphinx_identifier
56
+ )
57
+
58
+ set_source_database_settings source
59
+ set_source_attributes source, offset
60
+ set_source_sql source, offset
61
+ set_source_settings source
62
+
63
+ source
51
64
  end
52
65
 
53
- def to_config(model, index, database_conf, charset_type, offset)
54
- # Set up associations and joins
55
- add_internal_attributes
66
+ def to_riddle_for_delta(offset, index)
67
+ add_internal_attributes_and_facets
56
68
  link!
57
69
 
58
- attr_sources = attributes.collect { |attrib|
59
- attrib.to_sphinx_clause
60
- }.join("\n ")
61
-
62
- db_adapter = case adapter
63
- when :postgres
64
- "pgsql"
65
- when :mysql
66
- "mysql"
67
- else
68
- raise "Unsupported Database Adapter: Sphinx only supports MySQL and PosgreSQL"
69
- end
70
+ source = Riddle::Configuration::SQLSource.new(
71
+ "#{name}_delta_#{index}", adapter.sphinx_identifier
72
+ )
73
+ source.parent = "#{name}_core_#{index}"
70
74
 
71
- config = <<-SOURCE
72
-
73
- source #{self.class.name(model)}_#{index}_core
74
- {
75
- type = #{db_adapter}
76
- sql_host = #{database_conf[:host] || "localhost"}
77
- sql_user = #{database_conf[:username] || database_conf[:user]}
78
- sql_pass = #{(database_conf[:password] || "").gsub('#', '\#')}
79
- sql_db = #{database_conf[:database]}
80
- #{"sql_sock = #{database_conf[:socket]}" unless database_conf[:socket].blank? }
81
-
82
- sql_query_pre = #{charset_type == "utf-8" && adapter == :mysql ? "SET NAMES utf8" : ""}
83
- #{"sql_query_pre = SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}" if @options[:group_concat_max_len]}
84
- sql_query_pre = #{to_sql_query_pre}
85
- sql_query = #{to_sql(:offset => offset).gsub(/\n/, ' ')}
86
- sql_query_range = #{to_sql_query_range :offset => offset}
87
- sql_query_info = #{to_sql_query_info(offset)}
88
- #{attr_sources}
89
- }
90
- SOURCE
91
-
92
- if delta?
93
- config += <<-SOURCE
94
-
95
- source #{self.class.name(model)}_#{index}_delta : #{self.class.name(model)}_#{index}_core
96
- {
97
- sql_query_pre =
98
- sql_query_pre = #{charset_type == "utf-8" && adapter == :mysql ? "SET NAMES utf8" : ""}
99
- #{"sql_query_pre = SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}" if @options[:group_concat_max_len]}
100
- sql_query = #{to_sql(:delta => true, :offset => offset).gsub(/\n/, ' ')}
101
- sql_query_range = #{to_sql_query_range :offset => offset, :delta => true}
102
- }
103
- SOURCE
104
- end
75
+ set_source_database_settings source
76
+ set_source_attributes source, offset
77
+ set_source_sql source, offset, true
105
78
 
106
- config
79
+ source
107
80
  end
108
81
 
109
82
  # Link all the fields and associations to their corresponding
@@ -133,111 +106,16 @@ sql_query_range = #{to_sql_query_range :offset => offset, :delta => true}
133
106
  }
134
107
  end
135
108
 
136
- # Generates the big SQL statement to get the data back for all the fields
137
- # and attributes, using all the relevant association joins. If you want
138
- # the version filtered for delta values, send through :delta => true in the
139
- # options. Won't do much though if the index isn't set up to support a
140
- # delta sibling.
141
- #
142
- # Examples:
143
- #
144
- # index.to_sql
145
- # index.to_sql(:delta => true)
146
- #
147
- def to_sql(options={})
148
- assocs = all_associations
149
-
150
- where_clause = ""
151
- if self.delta?
152
- where_clause << " AND #{@model.quoted_table_name}.#{quote_column('delta')}" +" = #{options[:delta] ? db_boolean(true) : db_boolean(false)}"
153
- end
154
- unless @conditions.empty?
155
- where_clause << " AND " << @conditions.join(" AND ")
156
- end
157
-
158
- unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
159
-
160
- sql = <<-SQL
161
- SELECT #{ (
162
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
163
- @fields.collect { |field| field.to_select_sql } +
164
- @attributes.collect { |attribute| attribute.to_select_sql }
165
- ).join(", ") }
166
- FROM #{ @model.table_name }
167
- #{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
168
- WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} >= $start
169
- AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} <= $end
170
- #{ where_clause }
171
- GROUP BY #{ (
172
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
173
- @fields.collect { |field| field.to_group_sql }.compact +
174
- @attributes.collect { |attribute| attribute.to_group_sql }.compact
175
- ).join(", ") }
176
- SQL
177
-
178
- if @model.connection.class.name == "ActiveRecord::ConnectionAdapters::MysqlAdapter"
179
- sql += " ORDER BY NULL"
180
- end
181
-
182
- sql
183
- end
184
-
185
- # Simple helper method for the query info SQL - which is a statement that
186
- # returns the single row for a corresponding id.
187
- #
188
- def to_sql_query_info(offset)
189
- "SELECT * FROM #{@model.quoted_table_name} WHERE " +
190
- " #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
191
- end
192
-
193
- # Simple helper method for the query range SQL - which is a statement that
194
- # returns minimum and maximum id values. These can be filtered by delta -
195
- # so pass in :delta => true to get the delta version of the SQL.
196
- #
197
- def to_sql_query_range(options={})
198
- unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
199
-
200
- min_statement = "MIN(#{quote_column(@model.primary_key)} #{unique_id_expr})"
201
- max_statement = "MAX(#{quote_column(@model.primary_key)} #{unique_id_expr})"
202
-
203
- # Fix to handle Sphinx PostgreSQL bug (it doesn't like NULLs or 0's)
204
- if adapter == :postgres
205
- min_statement = "COALESCE(#{min_statement}, 1)"
206
- max_statement = "COALESCE(#{max_statement}, 1)"
207
- end
208
-
209
- sql = "SELECT #{min_statement}, #{max_statement} " +
210
- "FROM #{@model.quoted_table_name} "
211
- sql << "WHERE #{@model.quoted_table_name}.#{quote_column('delta')} " +
212
- "= #{options[:delta] ? db_boolean(true) : db_boolean(false)}" if self.delta?
213
- sql
214
- end
215
-
216
- # Returns the SQL query to run before a full index - ie: nothing unless the
217
- # index has a delta, and then it's an update statement to set delta values
218
- # back to 0.
219
- #
220
- def to_sql_query_pre
221
- self.delta? ? "UPDATE #{@model.quoted_table_name} SET #{quote_column('delta')} = #{db_boolean(false)}" : ""
222
- end
223
-
224
109
  # Flag to indicate whether this index has a corresponding delta index.
225
110
  #
226
111
  def delta?
227
- @delta
112
+ !@delta_object.nil?
228
113
  end
229
114
 
230
115
  def adapter
231
- @adapter ||= case @model.connection.class.name
232
- when "ActiveRecord::ConnectionAdapters::MysqlAdapter"
233
- :mysql
234
- when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
235
- :postgres
236
- else
237
- raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
238
- end
116
+ @adapter ||= @model.sphinx_database_adapter
239
117
  end
240
-
118
+
241
119
  def prefix_fields
242
120
  @fields.select { |field| field.prefixes }
243
121
  end
@@ -245,11 +123,23 @@ GROUP BY #{ (
245
123
  def infix_fields
246
124
  @fields.select { |field| field.infixes }
247
125
  end
126
+
127
+ def index_options
128
+ all_index_options = ThinkingSphinx::Configuration.instance.index_options.clone
129
+ @options.keys.select { |key|
130
+ ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s)
131
+ }.each { |key| all_index_options[key.to_sym] = @options[key] }
132
+ all_index_options
133
+ end
134
+
135
+ def quote_column(column)
136
+ @model.connection.quote_column_name(column)
137
+ end
248
138
 
249
139
  private
250
140
 
251
- def quote_column(column)
252
- @model.connection.quote_column_name(column)
141
+ def utf8?
142
+ self.index_options[:charset_type] == "utf-8"
253
143
  end
254
144
 
255
145
  # Does all the magic with the block provided to the base #initialize.
@@ -269,12 +159,33 @@ GROUP BY #{ (
269
159
  stored_class = @model.store_full_sti_class ? @model.name : @model.name.demodulize
270
160
  builder.where("#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)} = '#{stored_class}'")
271
161
  end
272
-
273
- @fields = builder.fields
274
- @attributes = builder.attributes
275
- @conditions = builder.conditions
276
- @delta = builder.properties[:delta]
277
- @options = builder.properties.except(:delta)
162
+
163
+ set_model = Proc.new { |item| item.model = @model }
164
+
165
+ @fields = builder.fields &set_model
166
+ @attributes = builder.attributes.each &set_model
167
+ @conditions = builder.conditions
168
+ @groupings = builder.groupings
169
+ @delta_object = ThinkingSphinx::Deltas.parse self, builder.properties
170
+ @options = builder.properties
171
+
172
+ is_faceted = Proc.new { |item| item.faceted }
173
+ add_facet = Proc.new { |item| @model.sphinx_facets << item.to_facet }
174
+
175
+ @model.sphinx_facets ||= []
176
+ @fields.select( &is_faceted).each &add_facet
177
+ @attributes.select(&is_faceted).each &add_facet
178
+
179
+ # We want to make sure that if the database doesn't exist, then Thinking
180
+ # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
181
+ # and db:migrate). It's a bit hacky, but I can't think of a better way.
182
+ rescue StandardError => err
183
+ case err.class.name
184
+ when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
185
+ return
186
+ else
187
+ raise err
188
+ end
278
189
  end
279
190
 
280
191
  # Returns all associations used amongst all the fields and attributes.
@@ -289,8 +200,8 @@ GROUP BY #{ (
289
200
  }.flatten +
290
201
  # attribute associations
291
202
  @attributes.collect { |attrib|
292
- attrib.associations.values
293
- }.flatten
203
+ attrib.associations.values if attrib.include_as_association?
204
+ }.compact.flatten
294
205
  ).uniq.collect { |assoc|
295
206
  # get ancestors as well as column-level associations
296
207
  assoc.ancestors
@@ -324,53 +235,189 @@ GROUP BY #{ (
324
235
  @associations[key] ||= Association.children(@model, key)
325
236
  end
326
237
 
327
- # Returns the proper boolean value string literal for the
328
- # current database adapter.
329
- #
330
- def db_boolean(val)
331
- if adapter == :postgres
332
- val ? 'TRUE' : 'FALSE'
238
+ def crc_column
239
+ if @model.column_names.include?(@model.inheritance_column)
240
+ adapter.cast_to_unsigned(adapter.convert_nulls(
241
+ adapter.crc(adapter.quote_with_table(@model.inheritance_column), true),
242
+ @model.to_crc32
243
+ ))
333
244
  else
334
- val ? '1' : '0'
245
+ @model.to_crc32.to_s
335
246
  end
336
247
  end
337
248
 
338
- def crc_column
339
- if adapter == :postgres
340
- @model.to_crc32.to_s
341
- elsif @model.column_names.include?(@model.inheritance_column)
342
- "IFNULL(CRC32(#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}), #{@model.to_crc32.to_s})"
249
+ def add_internal_attributes_and_facets
250
+ add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key.to_sym
251
+ add_internal_attribute :class_crc, :integer, crc_column, true
252
+ add_internal_attribute :subclass_crcs, :multi, subclasses_to_s
253
+ add_internal_attribute :sphinx_deleted, :integer, "0"
254
+
255
+ add_internal_facet :class_crc
256
+ end
257
+
258
+ def add_internal_attribute(name, type, contents, facet = false)
259
+ return unless attribute_by_alias(name).nil?
260
+
261
+ @attributes << Attribute.new(
262
+ FauxColumn.new(contents),
263
+ :type => type,
264
+ :as => name,
265
+ :facet => facet
266
+ )
267
+ end
268
+
269
+ def add_internal_facet(name)
270
+ return unless facet_by_alias(name).nil?
271
+
272
+ @model.sphinx_facets << ClassFacet.new(attribute_by_alias(name))
273
+ end
274
+
275
+ def attribute_by_alias(attr_alias)
276
+ @attributes.detect { |attrib| attrib.alias == attr_alias }
277
+ end
278
+
279
+ def facet_by_alias(name)
280
+ @model.sphinx_facets.detect { |facet| facet.name == name }
281
+ end
282
+
283
+ def subclasses_to_s
284
+ "'" + (@model.send(:subclasses).collect { |klass|
285
+ klass.to_crc32.to_s
286
+ } << @model.to_crc32.to_s).join(",") + "'"
287
+ end
288
+
289
+ def set_source_database_settings(source)
290
+ config = @model.connection.instance_variable_get(:@config)
291
+
292
+ source.sql_host = config[:host] || "localhost"
293
+ source.sql_user = config[:username] || config[:user] || ""
294
+ source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
295
+ source.sql_db = config[:database]
296
+ source.sql_port = config[:port]
297
+ source.sql_sock = config[:socket]
298
+ end
299
+
300
+ def set_source_attributes(source, offset = nil)
301
+ attributes.each do |attrib|
302
+ source.send(attrib.type_to_config) << attrib.config_value(offset)
303
+ end
304
+ end
305
+
306
+ def set_source_sql(source, offset, delta = false)
307
+ source.sql_query = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
308
+ source.sql_query_range = to_sql_query_range(:delta => delta)
309
+ source.sql_query_info = to_sql_query_info(offset)
310
+
311
+ source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
312
+
313
+ if @options[:group_concat_max_len]
314
+ source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
315
+ end
316
+
317
+ source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
318
+ end
319
+
320
+ def set_source_settings(source)
321
+ ThinkingSphinx::Configuration.instance.source_options.each do |key, value|
322
+ source.send("#{key}=".to_sym, value)
323
+ end
324
+
325
+ @options.each do |key, value|
326
+ source.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::SourceOptions.include?(key.to_s) && !value.nil?
327
+ end
328
+ end
329
+
330
+ def sql_query_pre_for_core
331
+ if self.delta? && !@delta_object.reset_query(@model).blank?
332
+ [@delta_object.reset_query(@model)]
343
333
  else
344
- @model.to_crc32.to_s
334
+ []
345
335
  end
346
336
  end
347
337
 
348
- def add_internal_attributes
349
- @attributes << Attribute.new(
350
- FauxColumn.new(:id),
351
- :type => :integer,
352
- :as => :sphinx_internal_id
353
- ) unless @attributes.detect { |attr| attr.alias == :sphinx_internal_id }
338
+ def sql_query_pre_for_delta
339
+ [""]
340
+ end
341
+
342
+ # Generates the big SQL statement to get the data back for all the fields
343
+ # and attributes, using all the relevant association joins. If you want
344
+ # the version filtered for delta values, send through :delta => true in the
345
+ # options. Won't do much though if the index isn't set up to support a
346
+ # delta sibling.
347
+ #
348
+ # Examples:
349
+ #
350
+ # index.to_sql
351
+ # index.to_sql(:delta => true)
352
+ #
353
+ def to_sql(options={})
354
+ assocs = all_associations
354
355
 
355
- @attributes << Attribute.new(
356
- FauxColumn.new(crc_column),
357
- :type => :integer,
358
- :as => :class_crc
359
- ) unless @attributes.detect { |attr| attr.alias == :class_crc }
356
+ where_clause = ""
357
+ if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
358
+ where_clause << " AND #{@delta_object.clause(@model, options[:delta])}"
359
+ end
360
+ unless @conditions.empty?
361
+ where_clause << " AND " << @conditions.join(" AND ")
362
+ end
360
363
 
361
- @attributes << Attribute.new(
362
- FauxColumn.new("'" + (@model.send(:subclasses).collect { |klass|
363
- klass.to_crc32.to_s
364
- } << @model.to_crc32.to_s).join(",") + "'"),
365
- :type => :multi,
366
- :as => :subclass_crcs
367
- ) unless @attributes.detect { |attr| attr.alias == :subclass_crcs }
364
+ internal_groupings = []
365
+ if @model.column_names.include?(@model.inheritance_column)
366
+ internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
367
+ end
368
368
 
369
- @attributes << Attribute.new(
370
- FauxColumn.new("0"),
371
- :type => :integer,
372
- :as => :sphinx_deleted
373
- ) unless @attributes.detect { |attr| attr.alias == :sphinx_deleted }
369
+ unique_id_expr = ThinkingSphinx.unique_id_expression(options[:offset])
370
+
371
+ sql = <<-SQL
372
+ SELECT #{ (
373
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
374
+ @fields.collect { |field| field.to_select_sql } +
375
+ @attributes.collect { |attribute| attribute.to_select_sql }
376
+ ).compact.join(", ") }
377
+ FROM #{ @model.table_name }
378
+ #{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
379
+ WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
380
+ AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
381
+ #{ where_clause }
382
+ GROUP BY #{ (
383
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
384
+ @fields.collect { |field| field.to_group_sql }.compact +
385
+ @attributes.collect { |attribute| attribute.to_group_sql }.compact +
386
+ @groupings + internal_groupings
387
+ ).join(", ") }
388
+ SQL
389
+
390
+ sql += " ORDER BY NULL" if adapter.sphinx_identifier == "mysql"
391
+ sql
392
+ end
393
+
394
+ # Simple helper method for the query info SQL - which is a statement that
395
+ # returns the single row for a corresponding id.
396
+ #
397
+ def to_sql_query_info(offset)
398
+ "SELECT * FROM #{@model.quoted_table_name} WHERE " +
399
+ " #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
400
+ end
401
+
402
+ # Simple helper method for the query range SQL - which is a statement that
403
+ # returns minimum and maximum id values. These can be filtered by delta -
404
+ # so pass in :delta => true to get the delta version of the SQL.
405
+ #
406
+ def to_sql_query_range(options={})
407
+ min_statement = adapter.convert_nulls(
408
+ "MIN(#{quote_column(@model.primary_key)})", 1
409
+ )
410
+ max_statement = adapter.convert_nulls(
411
+ "MAX(#{quote_column(@model.primary_key)})", 1
412
+ )
413
+
414
+ sql = "SELECT #{min_statement}, #{max_statement} " +
415
+ "FROM #{@model.quoted_table_name} "
416
+ if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
417
+ sql << "WHERE #{@delta_object.clause(@model, options[:delta])}"
418
+ end
419
+
420
+ sql
374
421
  end
375
422
  end
376
423
  end