dpickett-thinking-sphinx 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENCE +20 -0
- data/README +107 -0
- data/lib/thinking_sphinx/active_record/delta.rb +74 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
- data/lib/thinking_sphinx/active_record/search.rb +57 -0
- data/lib/thinking_sphinx/active_record.rb +245 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +34 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +53 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +129 -0
- data/lib/thinking_sphinx/association.rb +144 -0
- data/lib/thinking_sphinx/attribute.rb +254 -0
- data/lib/thinking_sphinx/class_facet.rb +20 -0
- data/lib/thinking_sphinx/collection.rb +142 -0
- data/lib/thinking_sphinx/configuration.rb +236 -0
- data/lib/thinking_sphinx/core/string.rb +22 -0
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +65 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +25 -0
- data/lib/thinking_sphinx/deltas.rb +22 -0
- data/lib/thinking_sphinx/facet.rb +58 -0
- data/lib/thinking_sphinx/facet_collection.rb +45 -0
- data/lib/thinking_sphinx/field.rb +172 -0
- data/lib/thinking_sphinx/index/builder.rb +233 -0
- data/lib/thinking_sphinx/index/faux_column.rb +110 -0
- data/lib/thinking_sphinx/index.rb +432 -0
- data/lib/thinking_sphinx/rails_additions.rb +133 -0
- data/lib/thinking_sphinx/search.rb +654 -0
- data/lib/thinking_sphinx/tasks.rb +128 -0
- data/lib/thinking_sphinx.rb +145 -0
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
- data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
- data/spec/unit/thinking_sphinx/active_record_spec.rb +256 -0
- data/spec/unit/thinking_sphinx/association_spec.rb +247 -0
- data/spec/unit/thinking_sphinx/attribute_spec.rb +212 -0
- data/spec/unit/thinking_sphinx/collection_spec.rb +14 -0
- data/spec/unit/thinking_sphinx/configuration_spec.rb +136 -0
- data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
- data/spec/unit/thinking_sphinx/field_spec.rb +145 -0
- data/spec/unit/thinking_sphinx/index/builder_spec.rb +5 -0
- data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +54 -0
- data/spec/unit/thinking_sphinx/search_spec.rb +59 -0
- data/spec/unit/thinking_sphinx_spec.rb +129 -0
- data/tasks/distribution.rb +48 -0
- data/tasks/rails.rake +1 -0
- data/tasks/testing.rb +86 -0
- data/vendor/after_commit/LICENSE +20 -0
- data/vendor/after_commit/README +16 -0
- data/vendor/after_commit/Rakefile +22 -0
- data/vendor/after_commit/init.rb +5 -0
- data/vendor/after_commit/lib/after_commit/active_record.rb +91 -0
- data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
- data/vendor/after_commit/lib/after_commit.rb +42 -0
- data/vendor/after_commit/test/after_commit_test.rb +53 -0
- data/vendor/delayed_job/lib/delayed/job.rb +251 -0
- data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
- data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
- data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
- data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
- data/vendor/riddle/lib/riddle/client/message.rb +65 -0
- data/vendor/riddle/lib/riddle/client/response.rb +84 -0
- data/vendor/riddle/lib/riddle/client.rb +619 -0
- data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
- data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
- data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
- data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
- data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
- data/vendor/riddle/lib/riddle/configuration/section.rb +37 -0
- data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
- data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
- data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
- data/vendor/riddle/lib/riddle/configuration.rb +33 -0
- data/vendor/riddle/lib/riddle/controller.rb +44 -0
- data/vendor/riddle/lib/riddle.rb +30 -0
- metadata +158 -0
@@ -0,0 +1,432 @@
|
|
1
|
+
require 'thinking_sphinx/index/builder'
|
2
|
+
require 'thinking_sphinx/index/faux_column'
|
3
|
+
|
4
|
+
module ThinkingSphinx
|
5
|
+
# The Index class is a ruby representation of a Sphinx source (not a Sphinx
|
6
|
+
# index - yes, I know it's a little confusing. You'll manage). This is
|
7
|
+
# another 'internal' Thinking Sphinx class - if you're using it directly,
|
8
|
+
# you either know what you're doing, or messing with things beyond your ken.
|
9
|
+
# Enjoy.
|
10
|
+
#
|
11
|
+
class Index
|
12
|
+
attr_accessor :model, :fields, :attributes, :conditions, :groupings,
|
13
|
+
:delta_object, :options
|
14
|
+
|
15
|
+
# Create a new index instance by passing in the model it is tied to, and
|
16
|
+
# a block to build it with (optional but recommended). For documentation
|
17
|
+
# on the syntax for inside the block, the Builder class is what you want.
|
18
|
+
#
|
19
|
+
# Quick Example:
|
20
|
+
#
|
21
|
+
# Index.new(User) do
|
22
|
+
# indexes login, email
|
23
|
+
#
|
24
|
+
# has created_at
|
25
|
+
#
|
26
|
+
# set_property :delta => true
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
def initialize(model, &block)
|
30
|
+
@model = model
|
31
|
+
@associations = {}
|
32
|
+
@fields = []
|
33
|
+
@attributes = []
|
34
|
+
@conditions = []
|
35
|
+
@groupings = []
|
36
|
+
@options = {}
|
37
|
+
@delta_object = nil
|
38
|
+
|
39
|
+
initialize_from_builder(&block) if block_given?
|
40
|
+
end
|
41
|
+
|
42
|
+
def name
|
43
|
+
self.class.name(@model)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.name(model)
|
47
|
+
model.name.underscore.tr(':/\\', '_')
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_riddle_for_core(offset, index)
|
51
|
+
add_internal_attributes
|
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
|
60
|
+
set_source_sql source, offset
|
61
|
+
set_source_settings source
|
62
|
+
|
63
|
+
source
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_riddle_for_delta(offset, index)
|
67
|
+
add_internal_attributes
|
68
|
+
link!
|
69
|
+
|
70
|
+
source = Riddle::Configuration::SQLSource.new(
|
71
|
+
"#{name}_delta_#{index}", adapter.sphinx_identifier
|
72
|
+
)
|
73
|
+
source.parent = "#{name}_core_#{index}"
|
74
|
+
|
75
|
+
set_source_database_settings source
|
76
|
+
set_source_attributes source
|
77
|
+
set_source_sql source, offset, true
|
78
|
+
|
79
|
+
source
|
80
|
+
end
|
81
|
+
|
82
|
+
# Link all the fields and associations to their corresponding
|
83
|
+
# associations and joins. This _must_ be called before interrogating
|
84
|
+
# the index's fields and associations for anything that may reference
|
85
|
+
# their SQL structure.
|
86
|
+
#
|
87
|
+
def link!
|
88
|
+
base = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(
|
89
|
+
@model, [], nil
|
90
|
+
)
|
91
|
+
|
92
|
+
@fields.each { |field|
|
93
|
+
field.model ||= @model
|
94
|
+
field.columns.each { |col|
|
95
|
+
field.associations[col] = associations(col.__stack.clone)
|
96
|
+
field.associations[col].each { |assoc| assoc.join_to(base) }
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
@attributes.each { |attribute|
|
101
|
+
attribute.model ||= @model
|
102
|
+
attribute.columns.each { |col|
|
103
|
+
attribute.associations[col] = associations(col.__stack.clone)
|
104
|
+
attribute.associations[col].each { |assoc| assoc.join_to(base) }
|
105
|
+
}
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
# Flag to indicate whether this index has a corresponding delta index.
|
110
|
+
#
|
111
|
+
def delta?
|
112
|
+
!@delta_object.nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
def adapter
|
116
|
+
@adapter ||= @model.sphinx_database_adapter
|
117
|
+
end
|
118
|
+
|
119
|
+
def prefix_fields
|
120
|
+
@fields.select { |field| field.prefixes }
|
121
|
+
end
|
122
|
+
|
123
|
+
def infix_fields
|
124
|
+
@fields.select { |field| field.infixes }
|
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
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def utf8?
|
142
|
+
self.index_options[:charset_type] == "utf-8"
|
143
|
+
end
|
144
|
+
|
145
|
+
# Does all the magic with the block provided to the base #initialize.
|
146
|
+
# Creates a new class subclassed from Builder, and evaluates the block
|
147
|
+
# on it, then pulls all relevant settings - fields, attributes, conditions,
|
148
|
+
# properties - into the new index.
|
149
|
+
#
|
150
|
+
# Also creates a CRC attribute for the model.
|
151
|
+
#
|
152
|
+
def initialize_from_builder(&block)
|
153
|
+
builder = Class.new(Builder)
|
154
|
+
builder.setup
|
155
|
+
|
156
|
+
builder.instance_eval &block
|
157
|
+
|
158
|
+
unless @model.descends_from_active_record?
|
159
|
+
stored_class = @model.store_full_sti_class ? @model.name : @model.name.demodulize
|
160
|
+
builder.where("#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)} = '#{stored_class}'")
|
161
|
+
end
|
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", "ActiveRecord::StatementInvalid"
|
185
|
+
return
|
186
|
+
else
|
187
|
+
raise err
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns all associations used amongst all the fields and attributes.
|
192
|
+
# This includes all associations between the model and what the actual
|
193
|
+
# columns are from.
|
194
|
+
#
|
195
|
+
def all_associations
|
196
|
+
@all_associations ||= (
|
197
|
+
# field associations
|
198
|
+
@fields.collect { |field|
|
199
|
+
field.associations.values
|
200
|
+
}.flatten +
|
201
|
+
# attribute associations
|
202
|
+
@attributes.collect { |attrib|
|
203
|
+
attrib.associations.values
|
204
|
+
}.flatten
|
205
|
+
).uniq.collect { |assoc|
|
206
|
+
# get ancestors as well as column-level associations
|
207
|
+
assoc.ancestors
|
208
|
+
}.flatten.uniq
|
209
|
+
end
|
210
|
+
|
211
|
+
# Gets a stack of associations for a specific path.
|
212
|
+
#
|
213
|
+
def associations(path, parent = nil)
|
214
|
+
assocs = []
|
215
|
+
|
216
|
+
if parent.nil?
|
217
|
+
assocs = association(path.shift)
|
218
|
+
else
|
219
|
+
assocs = parent.children(path.shift)
|
220
|
+
end
|
221
|
+
|
222
|
+
until path.empty?
|
223
|
+
point = path.shift
|
224
|
+
assocs = assocs.collect { |assoc|
|
225
|
+
assoc.children(point)
|
226
|
+
}.flatten
|
227
|
+
end
|
228
|
+
|
229
|
+
assocs
|
230
|
+
end
|
231
|
+
|
232
|
+
# Gets the association stack for a specific key.
|
233
|
+
#
|
234
|
+
def association(key)
|
235
|
+
@associations[key] ||= Association.children(@model, key)
|
236
|
+
end
|
237
|
+
|
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)),
|
242
|
+
@model.to_crc32
|
243
|
+
))
|
244
|
+
else
|
245
|
+
@model.to_crc32.to_s
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def add_internal_attributes
|
250
|
+
@attributes << Attribute.new(
|
251
|
+
FauxColumn.new(@model.primary_key.to_sym),
|
252
|
+
:type => :integer,
|
253
|
+
:as => :sphinx_internal_id
|
254
|
+
) unless @attributes.detect { |attr| attr.alias == :sphinx_internal_id }
|
255
|
+
|
256
|
+
unless @attributes.detect { |attr| attr.alias == :class_crc }
|
257
|
+
@attributes << Attribute.new(
|
258
|
+
FauxColumn.new(crc_column),
|
259
|
+
:type => :integer,
|
260
|
+
:as => :class_crc,
|
261
|
+
:facet => true
|
262
|
+
)
|
263
|
+
|
264
|
+
@model.sphinx_facets << ThinkingSphinx::ClassFacet.new(@attributes.last)
|
265
|
+
end
|
266
|
+
|
267
|
+
if @model.column_names.include?(@model.inheritance_column)
|
268
|
+
class_col = FauxColumn.new(
|
269
|
+
adapter.convert_nulls(adapter.quote_with_table(@model.inheritance_column), @model.to_s)
|
270
|
+
)
|
271
|
+
else
|
272
|
+
class_col = FauxColumn.new("'#{@model.to_s}'")
|
273
|
+
end
|
274
|
+
|
275
|
+
@attributes << Attribute.new(class_col,
|
276
|
+
:type => :string,
|
277
|
+
:as => :class
|
278
|
+
)
|
279
|
+
|
280
|
+
@attributes << Attribute.new(
|
281
|
+
FauxColumn.new("'" + (@model.send(:subclasses).collect { |klass|
|
282
|
+
klass.to_crc32.to_s
|
283
|
+
} << @model.to_crc32.to_s).join(",") + "'"),
|
284
|
+
:type => :multi,
|
285
|
+
:as => :subclass_crcs
|
286
|
+
) unless @attributes.detect { |attr| attr.alias == :subclass_crcs }
|
287
|
+
|
288
|
+
@attributes << Attribute.new(
|
289
|
+
FauxColumn.new("0"),
|
290
|
+
:type => :integer,
|
291
|
+
:as => :sphinx_deleted
|
292
|
+
) unless @attributes.detect { |attr| attr.alias == :sphinx_deleted }
|
293
|
+
end
|
294
|
+
|
295
|
+
def set_source_database_settings(source)
|
296
|
+
config = @model.connection.instance_variable_get(:@config)
|
297
|
+
|
298
|
+
source.sql_host = config[:host] || "localhost"
|
299
|
+
source.sql_user = config[:username] || config[:user] || ""
|
300
|
+
source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
|
301
|
+
source.sql_db = config[:database]
|
302
|
+
source.sql_port = config[:port]
|
303
|
+
source.sql_sock = config[:socket]
|
304
|
+
end
|
305
|
+
|
306
|
+
def set_source_attributes(source)
|
307
|
+
attributes.each do |attrib|
|
308
|
+
source.send(attrib.type_to_config) << attrib.config_value
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def set_source_sql(source, offset, delta = false)
|
313
|
+
source.sql_query = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
|
314
|
+
source.sql_query_range = to_sql_query_range(:delta => delta)
|
315
|
+
source.sql_query_info = to_sql_query_info(offset)
|
316
|
+
|
317
|
+
source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
|
318
|
+
|
319
|
+
if @options[:group_concat_max_len]
|
320
|
+
source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
|
321
|
+
end
|
322
|
+
|
323
|
+
source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
|
324
|
+
end
|
325
|
+
|
326
|
+
def set_source_settings(source)
|
327
|
+
ThinkingSphinx::Configuration.instance.source_options.each do |key, value|
|
328
|
+
source.send("#{key}=".to_sym, value)
|
329
|
+
end
|
330
|
+
|
331
|
+
@options.each do |key, value|
|
332
|
+
source.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::SourceOptions.include?(key.to_s) && !value.nil?
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def sql_query_pre_for_core
|
337
|
+
if self.delta? && !@delta_object.reset_query(@model).blank?
|
338
|
+
[@delta_object.reset_query(@model)]
|
339
|
+
else
|
340
|
+
[]
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def sql_query_pre_for_delta
|
345
|
+
[""]
|
346
|
+
end
|
347
|
+
|
348
|
+
# Generates the big SQL statement to get the data back for all the fields
|
349
|
+
# and attributes, using all the relevant association joins. If you want
|
350
|
+
# the version filtered for delta values, send through :delta => true in the
|
351
|
+
# options. Won't do much though if the index isn't set up to support a
|
352
|
+
# delta sibling.
|
353
|
+
#
|
354
|
+
# Examples:
|
355
|
+
#
|
356
|
+
# index.to_sql
|
357
|
+
# index.to_sql(:delta => true)
|
358
|
+
#
|
359
|
+
def to_sql(options={})
|
360
|
+
assocs = all_associations
|
361
|
+
|
362
|
+
where_clause = ""
|
363
|
+
if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
|
364
|
+
where_clause << " AND #{@delta_object.clause(@model, options[:delta])}"
|
365
|
+
end
|
366
|
+
unless @conditions.empty?
|
367
|
+
where_clause << " AND " << @conditions.join(" AND ")
|
368
|
+
end
|
369
|
+
|
370
|
+
internal_groupings = []
|
371
|
+
if @model.column_names.include?(@model.inheritance_column)
|
372
|
+
internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
|
373
|
+
end
|
374
|
+
|
375
|
+
unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
|
376
|
+
|
377
|
+
sql = <<-SQL
|
378
|
+
SELECT #{ (
|
379
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
|
380
|
+
@fields.collect { |field| field.to_select_sql } +
|
381
|
+
@attributes.collect { |attribute| attribute.to_select_sql }
|
382
|
+
).join(", ") }
|
383
|
+
FROM #{ @model.table_name }
|
384
|
+
#{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
|
385
|
+
WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
|
386
|
+
AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
|
387
|
+
#{ where_clause }
|
388
|
+
GROUP BY #{ (
|
389
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
|
390
|
+
@fields.collect { |field| field.to_group_sql }.compact +
|
391
|
+
@attributes.collect { |attribute| attribute.to_group_sql }.compact +
|
392
|
+
@groupings + internal_groupings
|
393
|
+
).join(", ") }
|
394
|
+
SQL
|
395
|
+
|
396
|
+
if @model.connection.class.name == "ActiveRecord::ConnectionAdapters::MysqlAdapter"
|
397
|
+
sql += " ORDER BY NULL"
|
398
|
+
end
|
399
|
+
|
400
|
+
sql
|
401
|
+
end
|
402
|
+
|
403
|
+
# Simple helper method for the query info SQL - which is a statement that
|
404
|
+
# returns the single row for a corresponding id.
|
405
|
+
#
|
406
|
+
def to_sql_query_info(offset)
|
407
|
+
"SELECT * FROM #{@model.quoted_table_name} WHERE " +
|
408
|
+
" #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
|
409
|
+
end
|
410
|
+
|
411
|
+
# Simple helper method for the query range SQL - which is a statement that
|
412
|
+
# returns minimum and maximum id values. These can be filtered by delta -
|
413
|
+
# so pass in :delta => true to get the delta version of the SQL.
|
414
|
+
#
|
415
|
+
def to_sql_query_range(options={})
|
416
|
+
min_statement = adapter.convert_nulls(
|
417
|
+
"MIN(#{quote_column(@model.primary_key)})", 1
|
418
|
+
)
|
419
|
+
max_statement = adapter.convert_nulls(
|
420
|
+
"MAX(#{quote_column(@model.primary_key)})", 1
|
421
|
+
)
|
422
|
+
|
423
|
+
sql = "SELECT #{min_statement}, #{max_statement} " +
|
424
|
+
"FROM #{@model.quoted_table_name} "
|
425
|
+
if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
|
426
|
+
sql << "WHERE #{@delta_object.clause(@model, options[:delta])}"
|
427
|
+
end
|
428
|
+
|
429
|
+
sql
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module HashExcept
|
3
|
+
# Returns a new hash without the given keys.
|
4
|
+
def except(*keys)
|
5
|
+
rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
|
6
|
+
reject { |key,| rejected.include?(key) }
|
7
|
+
end
|
8
|
+
|
9
|
+
# Replaces the hash without only the given keys.
|
10
|
+
def except!(*keys)
|
11
|
+
replace(except(*keys))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Hash.send(
|
17
|
+
:include, ThinkingSphinx::HashExcept
|
18
|
+
) unless Hash.instance_methods.include?("except")
|
19
|
+
|
20
|
+
module ThinkingSphinx
|
21
|
+
module ArrayExtractOptions
|
22
|
+
def extract_options!
|
23
|
+
last.is_a?(::Hash) ? pop : {}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Array.send(
|
29
|
+
:include, ThinkingSphinx::ArrayExtractOptions
|
30
|
+
) unless Array.instance_methods.include?("extract_options!")
|
31
|
+
|
32
|
+
module ThinkingSphinx
|
33
|
+
module AbstractQuotedTableName
|
34
|
+
def quote_table_name(name)
|
35
|
+
quote_column_name(name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.send(
|
41
|
+
:include, ThinkingSphinx::AbstractQuotedTableName
|
42
|
+
) unless ActiveRecord::ConnectionAdapters::AbstractAdapter.instance_methods.include?("quote_table_name")
|
43
|
+
|
44
|
+
module ThinkingSphinx
|
45
|
+
module MysqlQuotedTableName
|
46
|
+
def quote_table_name(name) #:nodoc:
|
47
|
+
quote_column_name(name).gsub('.', '`.`')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if ActiveRecord::ConnectionAdapters.constants.include?("MysqlAdapter")
|
53
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(
|
54
|
+
:include, ThinkingSphinx::MysqlQuotedTableName
|
55
|
+
) unless ActiveRecord::ConnectionAdapters::MysqlAdapter.instance_methods.include?("quote_table_name")
|
56
|
+
end
|
57
|
+
|
58
|
+
module ThinkingSphinx
|
59
|
+
module ActiveRecordQuotedName
|
60
|
+
def quoted_table_name
|
61
|
+
self.connection.quote_table_name(self.table_name)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
ActiveRecord::Base.extend(
|
67
|
+
ThinkingSphinx::ActiveRecordQuotedName
|
68
|
+
) unless ActiveRecord::Base.respond_to?("quoted_table_name")
|
69
|
+
|
70
|
+
module ThinkingSphinx
|
71
|
+
module ActiveRecordStoreFullSTIClass
|
72
|
+
def store_full_sti_class
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
ActiveRecord::Base.extend(
|
79
|
+
ThinkingSphinx::ActiveRecordStoreFullSTIClass
|
80
|
+
) unless ActiveRecord::Base.respond_to?(:store_full_sti_class)
|
81
|
+
|
82
|
+
module ThinkingSphinx
|
83
|
+
module ClassAttributeMethods
|
84
|
+
def cattr_reader(*syms)
|
85
|
+
syms.flatten.each do |sym|
|
86
|
+
next if sym.is_a?(Hash)
|
87
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
88
|
+
unless defined? @@#{sym}
|
89
|
+
@@#{sym} = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.#{sym}
|
93
|
+
@@#{sym}
|
94
|
+
end
|
95
|
+
|
96
|
+
def #{sym}
|
97
|
+
@@#{sym}
|
98
|
+
end
|
99
|
+
EOS
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def cattr_writer(*syms)
|
104
|
+
options = syms.extract_options!
|
105
|
+
syms.flatten.each do |sym|
|
106
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
107
|
+
unless defined? @@#{sym}
|
108
|
+
@@#{sym} = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.#{sym}=(obj)
|
112
|
+
@@#{sym} = obj
|
113
|
+
end
|
114
|
+
|
115
|
+
#{"
|
116
|
+
def #{sym}=(obj)
|
117
|
+
@@#{sym} = obj
|
118
|
+
end
|
119
|
+
" unless options[:instance_writer] == false }
|
120
|
+
EOS
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def cattr_accessor(*syms)
|
125
|
+
cattr_reader(*syms)
|
126
|
+
cattr_writer(*syms)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
Class.extend(
|
132
|
+
ThinkingSphinx::ClassAttributeMethods
|
133
|
+
) unless Class.respond_to?(:cattr_reader)
|