dpickett-thinking-sphinx 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|