freelancing-god-thinking-sphinx 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -33,22 +33,27 @@ module ThinkingSphinx
33
33
  end
34
34
 
35
35
  def self.instances_from_matches(matches, options = {})
36
- return matches.collect { |match|
37
- instance_from_match match, options
38
- } unless klass = options[:class]
39
-
36
+ if klass = options[:class]
37
+ instances_from_class klass, matches, options
38
+ else
39
+ instances_from_classes matches, options
40
+ end
41
+ end
42
+
43
+ def self.instances_from_class(klass, matches, options = {})
40
44
  index_options = klass.sphinx_index_options
41
-
45
+
42
46
  ids = matches.collect { |match| match[:attributes]["sphinx_internal_id"] }
43
47
  instances = ids.length > 0 ? klass.find(
44
48
  :all,
45
49
  :conditions => {klass.primary_key.to_sym => ids},
46
50
  :include => (options[:include] || index_options[:include]),
47
- :select => (options[:select] || index_options[:select])
51
+ :select => (options[:select] || index_options[:select])
48
52
  ) : []
49
-
50
- # Raise an exception if we find records in Sphinx but not in the DB, so the search method
51
- # can retry without them. See ThinkingSphinx::Search.retry_search_on_stale_index.
53
+
54
+ # Raise an exception if we find records in Sphinx but not in the DB, so
55
+ # the search method can retry without them. See
56
+ # ThinkingSphinx::Search.retry_search_on_stale_index.
52
57
  if options[:raise_on_stale] && instances.length < ids.length
53
58
  stale_ids = ids - instances.map {|i| i.id }
54
59
  raise StaleIdsException, stale_ids
@@ -59,14 +64,24 @@ module ThinkingSphinx
59
64
  }
60
65
  end
61
66
 
62
- def self.instance_from_match(match, options)
63
- class_from_crc(match[:attributes]["class_crc"]).find(
64
- match[:attributes]["sphinx_internal_id"],
65
- :include => options[:include],
66
- :select => options[:select]
67
- )
68
- rescue ::ActiveRecord::RecordNotFound
69
- nil
67
+ # Group results by class and call #find(:all) once for each group to reduce
68
+ # the number of #find's in multi-model searches.
69
+ #
70
+ def self.instances_from_classes(matches, options = {})
71
+ groups = matches.group_by { |match| match[:attributes]["class_crc"] }
72
+ groups.each do |crc, group|
73
+ group.replace(
74
+ instances_from_class(class_from_crc(crc), group, options)
75
+ )
76
+ end
77
+
78
+ matches.collect do |match|
79
+ groups.detect { |crc, group|
80
+ crc == match[:attributes]["class_crc"]
81
+ }[1].detect { |obj|
82
+ obj.id == match[:attributes]["sphinx_internal_id"]
83
+ }
84
+ end
70
85
  end
71
86
 
72
87
  def self.class_from_crc(crc)
@@ -98,9 +113,9 @@ module ThinkingSphinx
98
113
  each_with_attribute method.to_s.gsub(/^each_with_/, ''), &block
99
114
  end
100
115
 
101
- def each_with_group_and_count(&block)
116
+ def each_with_groupby_and_count(&block)
102
117
  results[:matches].each_with_index do |match, index|
103
- yield self[index], match[:attributes]["@group"], match[:attributes]["@count"]
118
+ yield self[index], match[:attributes]["@groupby"], match[:attributes]["@count"]
104
119
  end
105
120
  end
106
121
 
@@ -156,31 +156,6 @@ module ThinkingSphinx
156
156
  end
157
157
  end
158
158
 
159
- def hash_to_config(hash)
160
- hash.collect { |key, value|
161
- translated_value = case value
162
- when TrueClass
163
- "1"
164
- when FalseClass
165
- "0"
166
- when NilClass, ""
167
- next
168
- else
169
- value
170
- end
171
- " #{key} = #{translated_value}"
172
- }.join("\n")
173
- end
174
-
175
- def self.options_merge(base, extra)
176
- base = base.clone
177
- extra.each do |key, value|
178
- next if value.nil? || value == ""
179
- base[key] = value
180
- end
181
- base
182
- end
183
-
184
159
  def address
185
160
  @configuration.searchd.address
186
161
  end
@@ -0,0 +1,22 @@
1
+ module ThinkingSphinx
2
+ module Core
3
+ module String
4
+
5
+ def to_crc32
6
+ result = 0xFFFFFFFF
7
+ self.each_byte do |byte|
8
+ result ^= byte
9
+ 8.times do
10
+ result = (result >> 1) ^ (0xEDB88320 * (result & 1))
11
+ end
12
+ end
13
+ result ^ 0xFFFFFFFF
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+
20
+ class String
21
+ include ThinkingSphinx::Core::String
22
+ end
@@ -40,22 +40,11 @@ module ThinkingSphinx
40
40
  def clause(model, toggled)
41
41
  if toggled
42
42
  "#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" +
43
- " > #{time_difference}"
43
+ " > #{adapter.time_difference(@threshold)}"
44
44
  else
45
45
  nil
46
46
  end
47
47
  end
48
-
49
- def time_difference
50
- case @index.adapter
51
- when :mysql
52
- "DATE_SUB(NOW(), INTERVAL #{@threshold} SECOND)"
53
- when :postgres
54
- "current_timestamp - interval '#{@threshold} seconds'"
55
- else
56
- raise "Unknown Database Adapter"
57
- end
58
- end
59
48
  end
60
49
  end
61
50
  end
@@ -37,12 +37,12 @@ module ThinkingSphinx
37
37
 
38
38
  def reset_query(model)
39
39
  "UPDATE #{model.quoted_table_name} SET " +
40
- "#{@index.quote_column(@column.to_s)} = #{@index.db_boolean(false)}"
40
+ "#{@index.quote_column(@column.to_s)} = #{adapter.boolean(false)}"
41
41
  end
42
42
 
43
43
  def clause(model, toggled)
44
44
  "#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" +
45
- " = #{@index.db_boolean(toggled)}"
45
+ " = #{adapter.boolean(toggled)}"
46
46
  end
47
47
 
48
48
  protected
@@ -53,7 +53,13 @@ module ThinkingSphinx
53
53
 
54
54
  def delta_index_name(model)
55
55
  "#{model.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_delta"
56
- end
56
+ end
57
+
58
+ private
59
+
60
+ def adapter
61
+ @adapter = @index.model.sphinx_database_adapter
62
+ end
57
63
  end
58
64
  end
59
65
  end
@@ -8,7 +8,8 @@ module ThinkingSphinx
8
8
  # associations. Which can get messy. Use Index.link!, it really helps.
9
9
  #
10
10
  class Field
11
- attr_accessor :alias, :columns, :sortable, :associations, :model, :infixes, :prefixes
11
+ attr_accessor :alias, :columns, :sortable, :associations, :model, :infixes,
12
+ :prefixes, :faceted
12
13
 
13
14
  # To create a new field, you'll need to pass in either a single Column
14
15
  # or an array of them, and some (optional) options. The columns are
@@ -58,10 +59,11 @@ module ThinkingSphinx
58
59
 
59
60
  raise "Cannot define a field with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) }
60
61
 
61
- @alias = options[:as]
62
- @sortable = options[:sortable] || false
63
- @infixes = options[:infixes] || false
64
- @prefixes = options[:prefixes] || false
62
+ @alias = options[:as]
63
+ @sortable = options[:sortable] || false
64
+ @infixes = options[:infixes] || false
65
+ @prefixes = options[:prefixes] || false
66
+ @faceted = options[:facet] || false
65
67
  end
66
68
 
67
69
  # Get the part of the SELECT clause related to this field. Don't forget
@@ -75,10 +77,10 @@ module ThinkingSphinx
75
77
  column_with_prefix(column)
76
78
  }.join(', ')
77
79
 
78
- clause = concatenate(clause) if concat_ws?
79
- clause = group_concatenate(clause) if is_many?
80
+ clause = adapter.concatenate(clause) if concat_ws?
81
+ clause = adapter.group_concatenate(clause) if is_many?
80
82
 
81
- "#{cast_to_string clause } AS #{quote_column(unique_name)}"
83
+ "#{adapter.cast_to_string clause } AS #{quote_column(unique_name)}"
82
84
  end
83
85
 
84
86
  # Get the part of the GROUP BY clause related to this field - if one is
@@ -110,41 +112,16 @@ module ThinkingSphinx
110
112
  end
111
113
  end
112
114
 
113
- private
114
-
115
- def concatenate(clause)
116
- case @model.connection.class.name
117
- when "ActiveRecord::ConnectionAdapters::MysqlAdapter"
118
- "CONCAT_WS(' ', #{clause})"
119
- when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
120
- clause.split(', ').collect { |field|
121
- "COALESCE(#{field}, '')"
122
- }.join(" || ' ' || ")
123
- else
124
- clause
125
- end
115
+ def to_facet
116
+ return nil unless @faceted
117
+
118
+ ThinkingSphinx::Facet.new(unique_name, @columns, self)
126
119
  end
127
120
 
128
- def group_concatenate(clause)
129
- case @model.connection.class.name
130
- when "ActiveRecord::ConnectionAdapters::MysqlAdapter"
131
- "GROUP_CONCAT(#{clause} SEPARATOR ' ')"
132
- when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
133
- "array_to_string(array_accum(#{clause}), ' ')"
134
- else
135
- clause
136
- end
137
- end
121
+ private
138
122
 
139
- def cast_to_string(clause)
140
- case @model.connection.class.name
141
- when "ActiveRecord::ConnectionAdapters::MysqlAdapter"
142
- "CAST(#{clause} AS CHAR)"
143
- when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
144
- clause
145
- else
146
- clause
147
- end
123
+ def adapter
124
+ @adapter ||= @model.sphinx_database_adapter
148
125
  end
149
126
 
150
127
  def quote_column(column)
@@ -158,16 +135,7 @@ module ThinkingSphinx
158
135
  def concat_ws?
159
136
  @columns.length > 1 || multiple_associations?
160
137
  end
161
-
162
- # Checks the association tree for each column - if they're all the same,
163
- # returns false.
164
- #
165
- def multiple_sources?
166
- first = associations[@columns.first]
167
-
168
- !@columns.all? { |col| associations[col] == first }
169
- end
170
-
138
+
171
139
  # Checks whether any column requires multiple associations (which only
172
140
  # happens for polymorphic situations).
173
141
  #
@@ -200,9 +168,5 @@ module ThinkingSphinx
200
168
  def is_many?
201
169
  associations.values.flatten.any? { |assoc| assoc.is_many? }
202
170
  end
203
-
204
- def is_string?
205
- columns.all? { |col| col.is_string? }
206
- end
207
171
  end
208
172
  end
@@ -46,18 +46,13 @@ module ThinkingSphinx
46
46
  def self.name(model)
47
47
  model.name.underscore.tr(':/\\', '_')
48
48
  end
49
-
50
- def to_riddle(model, index, offset)
51
- add_internal_attributes
52
- link!
53
- end
54
49
 
55
50
  def to_riddle_for_core(offset, index)
56
51
  add_internal_attributes
57
52
  link!
58
53
 
59
54
  source = Riddle::Configuration::SQLSource.new(
60
- "#{name}_core_#{index}", riddle_adapter
55
+ "#{name}_core_#{index}", adapter.sphinx_identifier
61
56
  )
62
57
 
63
58
  set_source_database_settings source
@@ -73,7 +68,7 @@ module ThinkingSphinx
73
68
  link!
74
69
 
75
70
  source = Riddle::Configuration::SQLSource.new(
76
- "#{name}_delta_#{index}", riddle_adapter
71
+ "#{name}_delta_#{index}", adapter.sphinx_identifier
77
72
  )
78
73
  source.parent = "#{name}_core_#{index}"
79
74
 
@@ -111,92 +106,6 @@ module ThinkingSphinx
111
106
  }
112
107
  end
113
108
 
114
- # Generates the big SQL statement to get the data back for all the fields
115
- # and attributes, using all the relevant association joins. If you want
116
- # the version filtered for delta values, send through :delta => true in the
117
- # options. Won't do much though if the index isn't set up to support a
118
- # delta sibling.
119
- #
120
- # Examples:
121
- #
122
- # index.to_sql
123
- # index.to_sql(:delta => true)
124
- #
125
- def to_sql(options={})
126
- assocs = all_associations
127
-
128
- where_clause = ""
129
- if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
130
- where_clause << " AND #{@delta_object.clause(@model, options[:delta])}"
131
- end
132
- unless @conditions.empty?
133
- where_clause << " AND " << @conditions.join(" AND ")
134
- end
135
-
136
- internal_groupings = []
137
- if @model.column_names.include?(@model.inheritance_column)
138
- internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
139
- end
140
-
141
- unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
142
-
143
- sql = <<-SQL
144
- SELECT #{ (
145
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
146
- @fields.collect { |field| field.to_select_sql } +
147
- @attributes.collect { |attribute| attribute.to_select_sql }
148
- ).join(", ") }
149
- FROM #{ @model.table_name }
150
- #{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
151
- WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
152
- AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
153
- #{ where_clause }
154
- GROUP BY #{ (
155
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
156
- @fields.collect { |field| field.to_group_sql }.compact +
157
- @attributes.collect { |attribute| attribute.to_group_sql }.compact +
158
- @groupings + internal_groupings
159
- ).join(", ") }
160
- SQL
161
-
162
- if @model.connection.class.name == "ActiveRecord::ConnectionAdapters::MysqlAdapter"
163
- sql += " ORDER BY NULL"
164
- end
165
-
166
- sql
167
- end
168
-
169
- # Simple helper method for the query info SQL - which is a statement that
170
- # returns the single row for a corresponding id.
171
- #
172
- def to_sql_query_info(offset)
173
- "SELECT * FROM #{@model.quoted_table_name} WHERE " +
174
- " #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
175
- end
176
-
177
- # Simple helper method for the query range SQL - which is a statement that
178
- # returns minimum and maximum id values. These can be filtered by delta -
179
- # so pass in :delta => true to get the delta version of the SQL.
180
- #
181
- def to_sql_query_range(options={})
182
- min_statement = "MIN(#{quote_column(@model.primary_key)})"
183
- max_statement = "MAX(#{quote_column(@model.primary_key)})"
184
-
185
- # Fix to handle Sphinx PostgreSQL bug (it doesn't like NULLs or 0's)
186
- if adapter == :postgres
187
- min_statement = "COALESCE(#{min_statement}, 1)"
188
- max_statement = "COALESCE(#{max_statement}, 1)"
189
- end
190
-
191
- sql = "SELECT #{min_statement}, #{max_statement} " +
192
- "FROM #{@model.quoted_table_name} "
193
- if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
194
- sql << "WHERE #{@delta_object.clause(@model, options[:delta])}"
195
- end
196
-
197
- sql
198
- end
199
-
200
109
  # Flag to indicate whether this index has a corresponding delta index.
201
110
  #
202
111
  def delta?
@@ -204,14 +113,7 @@ GROUP BY #{ (
204
113
  end
205
114
 
206
115
  def adapter
207
- @adapter ||= case @model.connection.class.name
208
- when "ActiveRecord::ConnectionAdapters::MysqlAdapter"
209
- :mysql
210
- when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
211
- :postgres
212
- else
213
- raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
214
- end
116
+ @adapter ||= @model.sphinx_database_adapter
215
117
  end
216
118
 
217
119
  def prefix_fields
@@ -229,30 +131,11 @@ GROUP BY #{ (
229
131
  }.each { |key| all_index_options[key.to_sym] = @options[key] }
230
132
  all_index_options
231
133
  end
232
-
233
- def source_options
234
- all_source_options = ThinkingSphinx::Configuration.instance.source_options.clone
235
- @options.keys.select { |key|
236
- ThinkingSphinx::Configuration::SourceOptions.include?(key.to_s)
237
- }.each { |key| all_source_options[key.to_sym] = @options[key] }
238
- all_source_options
239
- end
240
-
134
+
241
135
  def quote_column(column)
242
136
  @model.connection.quote_column_name(column)
243
137
  end
244
138
 
245
- # Returns the proper boolean value string literal for the
246
- # current database adapter.
247
- #
248
- def db_boolean(val)
249
- if adapter == :postgres
250
- val ? 'TRUE' : 'FALSE'
251
- else
252
- val ? '1' : '0'
253
- end
254
- end
255
-
256
139
  private
257
140
 
258
141
  def utf8?
@@ -284,6 +167,14 @@ GROUP BY #{ (
284
167
  @delta_object = ThinkingSphinx::Deltas.parse self, builder.properties
285
168
  @options = builder.properties
286
169
 
170
+ @model.sphinx_facets ||= []
171
+ @fields.select { |field| field.faceted }.each { |field|
172
+ @model.sphinx_facets << field.to_facet
173
+ }
174
+ @attributes.select { |attrib| attrib.faceted }.each { |attrib|
175
+ @model.sphinx_facets << attrib.to_facet
176
+ }
177
+
287
178
  # We want to make sure that if the database doesn't exist, then Thinking
288
179
  # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
289
180
  # and db:migrate). It's a bit hacky, but I can't think of a better way.
@@ -345,12 +236,10 @@ GROUP BY #{ (
345
236
 
346
237
  def crc_column
347
238
  if @model.column_names.include?(@model.inheritance_column)
348
- case adapter
349
- when :postgres
350
- "COALESCE(crc32(#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}), #{@model.to_crc32.to_s})"
351
- when :mysql
352
- "CAST(IFNULL(CRC32(#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}), #{@model.to_crc32.to_s}) AS UNSIGNED)"
353
- end
239
+ adapter.cast_to_unsigned(adapter.convert_nulls(
240
+ adapter.crc(adapter.quote_with_table(@model.inheritance_column)),
241
+ @model.to_crc32
242
+ ))
354
243
  else
355
244
  @model.to_crc32.to_s
356
245
  end
@@ -383,24 +272,13 @@ GROUP BY #{ (
383
272
  :as => :sphinx_deleted
384
273
  ) unless @attributes.detect { |attr| attr.alias == :sphinx_deleted }
385
274
  end
386
-
387
- def riddle_adapter
388
- case adapter
389
- when :postgres
390
- "pgsql"
391
- when :mysql
392
- "mysql"
393
- else
394
- raise "Unsupported Database Adapter: Sphinx only supports MySQL and PosgreSQL"
395
- end
396
- end
397
-
275
+
398
276
  def set_source_database_settings(source)
399
277
  config = @model.connection.instance_variable_get(:@config)
400
278
 
401
- source.sql_host = config[:host] || "localhost"
402
- source.sql_user = config[:username] || config[:user]
403
- source.sql_pass = (config[:password] || "").gsub('#', '\#')
279
+ source.sql_host = config[:host] || "localhost"
280
+ source.sql_user = config[:username] || config[:user]
281
+ source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
404
282
  source.sql_db = config[:database]
405
283
  source.sql_port = config[:port]
406
284
  source.sql_sock = config[:socket]
@@ -423,9 +301,7 @@ GROUP BY #{ (
423
301
  source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
424
302
  end
425
303
 
426
- if utf8? && adapter == :mysql
427
- source.sql_query_pre << "SET NAMES utf8"
428
- end
304
+ source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
429
305
  end
430
306
 
431
307
  def set_source_settings(source)
@@ -449,5 +325,89 @@ GROUP BY #{ (
449
325
  def sql_query_pre_for_delta
450
326
  [""]
451
327
  end
328
+
329
+ # Generates the big SQL statement to get the data back for all the fields
330
+ # and attributes, using all the relevant association joins. If you want
331
+ # the version filtered for delta values, send through :delta => true in the
332
+ # options. Won't do much though if the index isn't set up to support a
333
+ # delta sibling.
334
+ #
335
+ # Examples:
336
+ #
337
+ # index.to_sql
338
+ # index.to_sql(:delta => true)
339
+ #
340
+ def to_sql(options={})
341
+ assocs = all_associations
342
+
343
+ where_clause = ""
344
+ if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
345
+ where_clause << " AND #{@delta_object.clause(@model, options[:delta])}"
346
+ end
347
+ unless @conditions.empty?
348
+ where_clause << " AND " << @conditions.join(" AND ")
349
+ end
350
+
351
+ internal_groupings = []
352
+ if @model.column_names.include?(@model.inheritance_column)
353
+ internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
354
+ end
355
+
356
+ unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
357
+
358
+ sql = <<-SQL
359
+ SELECT #{ (
360
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
361
+ @fields.collect { |field| field.to_select_sql } +
362
+ @attributes.collect { |attribute| attribute.to_select_sql }
363
+ ).join(", ") }
364
+ FROM #{ @model.table_name }
365
+ #{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
366
+ WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
367
+ AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
368
+ #{ where_clause }
369
+ GROUP BY #{ (
370
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
371
+ @fields.collect { |field| field.to_group_sql }.compact +
372
+ @attributes.collect { |attribute| attribute.to_group_sql }.compact +
373
+ @groupings + internal_groupings
374
+ ).join(", ") }
375
+ SQL
376
+
377
+ if @model.connection.class.name == "ActiveRecord::ConnectionAdapters::MysqlAdapter"
378
+ sql += " ORDER BY NULL"
379
+ end
380
+
381
+ sql
382
+ end
383
+
384
+ # Simple helper method for the query info SQL - which is a statement that
385
+ # returns the single row for a corresponding id.
386
+ #
387
+ def to_sql_query_info(offset)
388
+ "SELECT * FROM #{@model.quoted_table_name} WHERE " +
389
+ " #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
390
+ end
391
+
392
+ # Simple helper method for the query range SQL - which is a statement that
393
+ # returns minimum and maximum id values. These can be filtered by delta -
394
+ # so pass in :delta => true to get the delta version of the SQL.
395
+ #
396
+ def to_sql_query_range(options={})
397
+ min_statement = adapter.convert_nulls(
398
+ "MIN(#{quote_column(@model.primary_key)})", 1
399
+ )
400
+ max_statement = adapter.convert_nulls(
401
+ "MAX(#{quote_column(@model.primary_key)})", 1
402
+ )
403
+
404
+ sql = "SELECT #{min_statement}, #{max_statement} " +
405
+ "FROM #{@model.quoted_table_name} "
406
+ if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
407
+ sql << "WHERE #{@delta_object.clause(@model, options[:delta])}"
408
+ end
409
+
410
+ sql
411
+ end
452
412
  end
453
413
  end