pixeltrix-thinking-sphinx 1.1.5 → 1.2.1
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/README.textile +147 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
- data/lib/thinking_sphinx/active_record/delta.rb +14 -1
- data/lib/thinking_sphinx/active_record/scopes.rb +37 -0
- data/lib/thinking_sphinx/active_record.rb +46 -12
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +9 -1
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +3 -2
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +12 -5
- data/lib/thinking_sphinx/association.rb +20 -0
- data/lib/thinking_sphinx/attribute.rb +187 -116
- data/lib/thinking_sphinx/class_facet.rb +15 -0
- data/lib/thinking_sphinx/configuration.rb +46 -14
- data/lib/thinking_sphinx/core/string.rb +3 -10
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +3 -3
- data/lib/thinking_sphinx/deltas/default_delta.rb +9 -6
- data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +4 -2
- data/lib/thinking_sphinx/deltas.rb +14 -6
- data/lib/thinking_sphinx/deploy/capistrano.rb +98 -0
- data/lib/thinking_sphinx/excerpter.rb +22 -0
- data/lib/thinking_sphinx/facet.rb +68 -18
- data/lib/thinking_sphinx/facet_search.rb +134 -0
- data/lib/thinking_sphinx/field.rb +7 -97
- data/lib/thinking_sphinx/index/builder.rb +255 -201
- data/lib/thinking_sphinx/index.rb +28 -343
- data/lib/thinking_sphinx/property.rb +160 -0
- data/lib/thinking_sphinx/rails_additions.rb +7 -4
- data/lib/thinking_sphinx/search.rb +593 -587
- data/lib/thinking_sphinx/search_methods.rb +421 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
- data/lib/thinking_sphinx/source/sql.rb +128 -0
- data/lib/thinking_sphinx/source.rb +150 -0
- data/lib/thinking_sphinx/tasks.rb +45 -11
- data/lib/thinking_sphinx.rb +88 -14
- data/rails/init.rb +14 -0
- data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +7 -7
- data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
- data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +92 -0
- data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +115 -42
- data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +4 -5
- data/spec/lib/thinking_sphinx/attribute_spec.rb +465 -0
- data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +118 -7
- data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
- data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
- data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
- data/spec/lib/thinking_sphinx/facet_spec.rb +302 -0
- data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +26 -17
- data/spec/lib/thinking_sphinx/index/builder_spec.rb +355 -0
- data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
- data/spec/{unit → lib}/thinking_sphinx/index_spec.rb +3 -12
- data/spec/lib/thinking_sphinx/rails_additions_spec.rb +191 -0
- data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
- data/spec/lib/thinking_sphinx/search_spec.rb +887 -0
- data/spec/lib/thinking_sphinx/source_spec.rb +217 -0
- data/spec/{unit → lib}/thinking_sphinx_spec.rb +30 -8
- data/tasks/distribution.rb +20 -1
- data/tasks/testing.rb +7 -15
- data/vendor/after_commit/init.rb +3 -0
- data/vendor/after_commit/lib/after_commit/active_record.rb +27 -4
- data/vendor/after_commit/lib/after_commit/connection_adapters.rb +1 -1
- data/vendor/after_commit/lib/after_commit.rb +4 -1
- data/vendor/riddle/lib/riddle/client/message.rb +4 -3
- data/vendor/riddle/lib/riddle/client.rb +3 -0
- data/vendor/riddle/lib/riddle/configuration/section.rb +8 -2
- data/vendor/riddle/lib/riddle/controller.rb +1 -1
- data/vendor/riddle/lib/riddle.rb +1 -1
- metadata +75 -39
- data/README +0 -107
- data/lib/thinking_sphinx/active_record/search.rb +0 -57
- data/lib/thinking_sphinx/collection.rb +0 -142
- data/lib/thinking_sphinx/facet_collection.rb +0 -44
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
- data/spec/unit/thinking_sphinx/attribute_spec.rb +0 -212
- data/spec/unit/thinking_sphinx/collection_spec.rb +0 -14
- data/spec/unit/thinking_sphinx/index/builder_spec.rb +0 -5
- data/spec/unit/thinking_sphinx/search_spec.rb +0 -59
@@ -9,8 +9,7 @@ module ThinkingSphinx
|
|
9
9
|
# Enjoy.
|
10
10
|
#
|
11
11
|
class Index
|
12
|
-
attr_accessor :model, :
|
13
|
-
:delta_object, :options
|
12
|
+
attr_accessor :model, :sources, :delta_object
|
14
13
|
|
15
14
|
# Create a new index instance by passing in the model it is tied to, and
|
16
15
|
# a block to build it with (optional but recommended). For documentation
|
@@ -28,15 +27,17 @@ module ThinkingSphinx
|
|
28
27
|
#
|
29
28
|
def initialize(model, &block)
|
30
29
|
@model = model
|
31
|
-
@
|
32
|
-
@fields = []
|
33
|
-
@attributes = []
|
34
|
-
@conditions = []
|
35
|
-
@groupings = []
|
30
|
+
@sources = []
|
36
31
|
@options = {}
|
37
32
|
@delta_object = nil
|
38
|
-
|
39
|
-
|
33
|
+
end
|
34
|
+
|
35
|
+
def fields
|
36
|
+
@sources.collect { |source| source.fields }.flatten
|
37
|
+
end
|
38
|
+
|
39
|
+
def attributes
|
40
|
+
@sources.collect { |source| source.attributes }.flatten
|
40
41
|
end
|
41
42
|
|
42
43
|
def name
|
@@ -47,99 +48,39 @@ module ThinkingSphinx
|
|
47
48
|
model.name.underscore.tr(':/\\', '_')
|
48
49
|
end
|
49
50
|
|
50
|
-
def
|
51
|
-
|
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
|
51
|
+
def prefix_fields
|
52
|
+
fields.select { |field| field.prefixes }
|
80
53
|
end
|
81
54
|
|
82
|
-
|
83
|
-
|
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
|
-
}
|
55
|
+
def infix_fields
|
56
|
+
fields.select { |field| field.infixes }
|
107
57
|
end
|
108
58
|
|
109
|
-
|
110
|
-
|
111
|
-
def delta?
|
112
|
-
!@delta_object.nil?
|
59
|
+
def local_options
|
60
|
+
@options
|
113
61
|
end
|
114
62
|
|
115
|
-
def
|
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
|
63
|
+
def options
|
128
64
|
all_index_options = ThinkingSphinx::Configuration.instance.index_options.clone
|
129
65
|
@options.keys.select { |key|
|
130
|
-
ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s)
|
66
|
+
ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) ||
|
67
|
+
ThinkingSphinx::Configuration::CustomOptions.include?(key.to_s)
|
131
68
|
}.each { |key| all_index_options[key.to_sym] = @options[key] }
|
132
69
|
all_index_options
|
133
70
|
end
|
134
|
-
|
135
|
-
def
|
136
|
-
|
71
|
+
|
72
|
+
def delta?
|
73
|
+
!@delta_object.nil?
|
137
74
|
end
|
138
75
|
|
139
76
|
private
|
140
77
|
|
78
|
+
def adapter
|
79
|
+
@adapter ||= @model.sphinx_database_adapter
|
80
|
+
end
|
81
|
+
|
141
82
|
def utf8?
|
142
|
-
|
83
|
+
options[:charset_type] == "utf-8"
|
143
84
|
end
|
144
85
|
|
145
86
|
# Does all the magic with the block provided to the base #initialize.
|
@@ -147,268 +88,12 @@ module ThinkingSphinx
|
|
147
88
|
# on it, then pulls all relevant settings - fields, attributes, conditions,
|
148
89
|
# properties - into the new index.
|
149
90
|
#
|
150
|
-
# Also creates a CRC attribute for the model.
|
151
|
-
#
|
152
91
|
def initialize_from_builder(&block)
|
153
|
-
|
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
|
-
@attributes << Attribute.new(
|
257
|
-
FauxColumn.new(crc_column),
|
258
|
-
:type => :integer,
|
259
|
-
:as => :class_crc
|
260
|
-
) unless @attributes.detect { |attr| attr.alias == :class_crc }
|
261
|
-
|
262
|
-
@attributes << Attribute.new(
|
263
|
-
FauxColumn.new("'" + (@model.send(:subclasses).collect { |klass|
|
264
|
-
klass.to_crc32.to_s
|
265
|
-
} << @model.to_crc32.to_s).join(",") + "'"),
|
266
|
-
:type => :multi,
|
267
|
-
:as => :subclass_crcs
|
268
|
-
) unless @attributes.detect { |attr| attr.alias == :subclass_crcs }
|
269
|
-
|
270
|
-
@attributes << Attribute.new(
|
271
|
-
FauxColumn.new("0"),
|
272
|
-
:type => :integer,
|
273
|
-
:as => :sphinx_deleted
|
274
|
-
) unless @attributes.detect { |attr| attr.alias == :sphinx_deleted }
|
275
|
-
end
|
276
|
-
|
277
|
-
def set_source_database_settings(source)
|
278
|
-
config = @model.connection.instance_variable_get(:@config)
|
279
|
-
|
280
|
-
source.sql_host = config[:host] || "localhost"
|
281
|
-
source.sql_user = config[:username] || config[:user] || ""
|
282
|
-
source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
|
283
|
-
source.sql_db = config[:database]
|
284
|
-
source.sql_port = config[:port]
|
285
|
-
source.sql_sock = config[:socket]
|
286
|
-
end
|
287
|
-
|
288
|
-
def set_source_attributes(source)
|
289
|
-
attributes.each do |attrib|
|
290
|
-
source.send(attrib.type_to_config) << attrib.config_value
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
def set_source_sql(source, offset, delta = false)
|
295
|
-
source.sql_query = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
|
296
|
-
source.sql_query_range = to_sql_query_range(:delta => delta)
|
297
|
-
source.sql_query_info = to_sql_query_info(offset)
|
298
|
-
|
299
|
-
source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
|
300
|
-
|
301
|
-
if @options[:group_concat_max_len]
|
302
|
-
source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
|
303
|
-
end
|
304
|
-
|
305
|
-
source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
|
306
|
-
end
|
307
|
-
|
308
|
-
def set_source_settings(source)
|
309
|
-
ThinkingSphinx::Configuration.instance.source_options.each do |key, value|
|
310
|
-
source.send("#{key}=".to_sym, value)
|
311
|
-
end
|
312
|
-
|
313
|
-
@options.each do |key, value|
|
314
|
-
source.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::SourceOptions.include?(key.to_s) && !value.nil?
|
315
|
-
end
|
316
|
-
end
|
317
|
-
|
318
|
-
def sql_query_pre_for_core
|
319
|
-
if self.delta? && !@delta_object.reset_query(@model).blank?
|
320
|
-
[@delta_object.reset_query(@model)]
|
321
|
-
else
|
322
|
-
[]
|
323
|
-
end
|
92
|
+
#
|
324
93
|
end
|
325
94
|
|
326
95
|
def sql_query_pre_for_delta
|
327
96
|
[""]
|
328
97
|
end
|
329
|
-
|
330
|
-
# Generates the big SQL statement to get the data back for all the fields
|
331
|
-
# and attributes, using all the relevant association joins. If you want
|
332
|
-
# the version filtered for delta values, send through :delta => true in the
|
333
|
-
# options. Won't do much though if the index isn't set up to support a
|
334
|
-
# delta sibling.
|
335
|
-
#
|
336
|
-
# Examples:
|
337
|
-
#
|
338
|
-
# index.to_sql
|
339
|
-
# index.to_sql(:delta => true)
|
340
|
-
#
|
341
|
-
def to_sql(options={})
|
342
|
-
assocs = all_associations
|
343
|
-
|
344
|
-
where_clause = ""
|
345
|
-
if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
|
346
|
-
where_clause << " AND #{@delta_object.clause(@model, options[:delta])}"
|
347
|
-
end
|
348
|
-
unless @conditions.empty?
|
349
|
-
where_clause << " AND " << @conditions.join(" AND ")
|
350
|
-
end
|
351
|
-
|
352
|
-
internal_groupings = []
|
353
|
-
if @model.column_names.include?(@model.inheritance_column)
|
354
|
-
internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
|
355
|
-
end
|
356
|
-
|
357
|
-
unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
|
358
|
-
|
359
|
-
sql = <<-SQL
|
360
|
-
SELECT #{ (
|
361
|
-
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
|
362
|
-
@fields.collect { |field| field.to_select_sql } +
|
363
|
-
@attributes.collect { |attribute| attribute.to_select_sql }
|
364
|
-
).join(", ") }
|
365
|
-
FROM #{ @model.table_name }
|
366
|
-
#{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
|
367
|
-
WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
|
368
|
-
AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
|
369
|
-
#{ where_clause }
|
370
|
-
GROUP BY #{ (
|
371
|
-
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
|
372
|
-
@fields.collect { |field| field.to_group_sql }.compact +
|
373
|
-
@attributes.collect { |attribute| attribute.to_group_sql }.compact +
|
374
|
-
@groupings + internal_groupings
|
375
|
-
).join(", ") }
|
376
|
-
SQL
|
377
|
-
|
378
|
-
if @model.connection.class.name == "ActiveRecord::ConnectionAdapters::MysqlAdapter"
|
379
|
-
sql += " ORDER BY NULL"
|
380
|
-
end
|
381
|
-
|
382
|
-
sql
|
383
|
-
end
|
384
|
-
|
385
|
-
# Simple helper method for the query info SQL - which is a statement that
|
386
|
-
# returns the single row for a corresponding id.
|
387
|
-
#
|
388
|
-
def to_sql_query_info(offset)
|
389
|
-
"SELECT * FROM #{@model.quoted_table_name} WHERE " +
|
390
|
-
" #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
|
391
|
-
end
|
392
|
-
|
393
|
-
# Simple helper method for the query range SQL - which is a statement that
|
394
|
-
# returns minimum and maximum id values. These can be filtered by delta -
|
395
|
-
# so pass in :delta => true to get the delta version of the SQL.
|
396
|
-
#
|
397
|
-
def to_sql_query_range(options={})
|
398
|
-
min_statement = adapter.convert_nulls(
|
399
|
-
"MIN(#{quote_column(@model.primary_key)})", 1
|
400
|
-
)
|
401
|
-
max_statement = adapter.convert_nulls(
|
402
|
-
"MAX(#{quote_column(@model.primary_key)})", 1
|
403
|
-
)
|
404
|
-
|
405
|
-
sql = "SELECT #{min_statement}, #{max_statement} " +
|
406
|
-
"FROM #{@model.quoted_table_name} "
|
407
|
-
if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
|
408
|
-
sql << "WHERE #{@delta_object.clause(@model, options[:delta])}"
|
409
|
-
end
|
410
|
-
|
411
|
-
sql
|
412
|
-
end
|
413
98
|
end
|
414
99
|
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
class Property
|
3
|
+
attr_accessor :alias, :columns, :associations, :model, :faceted, :admin
|
4
|
+
|
5
|
+
def initialize(source, columns, options = {})
|
6
|
+
@source = source
|
7
|
+
@model = source.model
|
8
|
+
@columns = Array(columns)
|
9
|
+
@associations = {}
|
10
|
+
|
11
|
+
raise "Cannot define a field or attribute in #{source.model.name} 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) }
|
12
|
+
|
13
|
+
@alias = options[:as]
|
14
|
+
@faceted = options[:facet]
|
15
|
+
@admin = options[:admin]
|
16
|
+
|
17
|
+
@columns.each { |col|
|
18
|
+
@associations[col] = association_stack(col.__stack.clone).each { |assoc|
|
19
|
+
assoc.join_to(source.base)
|
20
|
+
}
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the unique name of the attribute - which is either the alias of
|
25
|
+
# the attribute, or the name of the only column - if there is only one. If
|
26
|
+
# there isn't, there should be an alias. Else things probably won't work.
|
27
|
+
# Consider yourself warned.
|
28
|
+
#
|
29
|
+
def unique_name
|
30
|
+
if @columns.length == 1
|
31
|
+
@alias || @columns.first.__name
|
32
|
+
else
|
33
|
+
@alias
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_facet
|
38
|
+
return nil unless @faceted
|
39
|
+
|
40
|
+
ThinkingSphinx::Facet.new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the part of the GROUP BY clause related to this attribute - if one is
|
44
|
+
# needed. If not, all you'll get back is nil. The latter will happen if
|
45
|
+
# there isn't actually a real column to get data from, or if there's
|
46
|
+
# multiple data values (read: a has_many or has_and_belongs_to_many
|
47
|
+
# association).
|
48
|
+
#
|
49
|
+
def to_group_sql
|
50
|
+
case
|
51
|
+
when is_many?, is_string?, ThinkingSphinx.use_group_by_shortcut?
|
52
|
+
nil
|
53
|
+
else
|
54
|
+
@columns.collect { |column|
|
55
|
+
column_with_prefix(column)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def changed?(instance)
|
61
|
+
return true if is_string? || @columns.any? { |col| !col.__stack.empty? }
|
62
|
+
|
63
|
+
!@columns.all? { |col|
|
64
|
+
instance.respond_to?("#{col.__name.to_s}_changed?") &&
|
65
|
+
!instance.send("#{col.__name.to_s}_changed?")
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def admin?
|
70
|
+
admin
|
71
|
+
end
|
72
|
+
|
73
|
+
def public?
|
74
|
+
!admin
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Could there be more than one value related to the parent record? If so,
|
80
|
+
# then this will return true. If not, false. It's that simple.
|
81
|
+
#
|
82
|
+
def is_many?
|
83
|
+
associations.values.flatten.any? { |assoc| assoc.is_many? }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns true if any of the columns are string values, instead of database
|
87
|
+
# column references.
|
88
|
+
def is_string?
|
89
|
+
columns.all? { |col| col.is_string? }
|
90
|
+
end
|
91
|
+
|
92
|
+
def adapter
|
93
|
+
@adapter ||= @model.sphinx_database_adapter
|
94
|
+
end
|
95
|
+
|
96
|
+
def quote_with_table(table, column)
|
97
|
+
"#{quote_table_name(table)}.#{quote_column(column)}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def quote_column(column)
|
101
|
+
@model.connection.quote_column_name(column)
|
102
|
+
end
|
103
|
+
|
104
|
+
def quote_table_name(table_name)
|
105
|
+
@model.connection.quote_table_name(table_name)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Indication of whether the columns should be concatenated with a space
|
109
|
+
# between each value. True if there's either multiple sources or multiple
|
110
|
+
# associations.
|
111
|
+
#
|
112
|
+
def concat_ws?
|
113
|
+
multiple_associations? || @columns.length > 1
|
114
|
+
end
|
115
|
+
|
116
|
+
# Checks whether any column requires multiple associations (which only
|
117
|
+
# happens for polymorphic situations).
|
118
|
+
#
|
119
|
+
def multiple_associations?
|
120
|
+
associations.any? { |col,assocs| assocs.length > 1 }
|
121
|
+
end
|
122
|
+
|
123
|
+
# Builds a column reference tied to the appropriate associations. This
|
124
|
+
# dives into the associations hash and their corresponding joins to
|
125
|
+
# figure out how to correctly reference a column in SQL.
|
126
|
+
#
|
127
|
+
def column_with_prefix(column)
|
128
|
+
if column.is_string?
|
129
|
+
column.__name
|
130
|
+
elsif associations[column].empty?
|
131
|
+
"#{@model.quoted_table_name}.#{quote_column(column.__name)}"
|
132
|
+
else
|
133
|
+
associations[column].collect { |assoc|
|
134
|
+
assoc.has_column?(column.__name) ?
|
135
|
+
"#{quote_with_table(assoc.join.aliased_table_name, column.__name)}" :
|
136
|
+
nil
|
137
|
+
}.compact.join(', ')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Gets a stack of associations for a specific path.
|
142
|
+
#
|
143
|
+
def association_stack(path, parent = nil)
|
144
|
+
assocs = []
|
145
|
+
|
146
|
+
if parent.nil?
|
147
|
+
assocs = @source.association(path.shift)
|
148
|
+
else
|
149
|
+
assocs = parent.children(path.shift)
|
150
|
+
end
|
151
|
+
|
152
|
+
until path.empty?
|
153
|
+
point = path.shift
|
154
|
+
assocs = assocs.collect { |assoc| assoc.children(point) }.flatten
|
155
|
+
end
|
156
|
+
|
157
|
+
assocs
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -49,10 +49,13 @@ module ThinkingSphinx
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
if ActiveRecord::ConnectionAdapters.constants.include?("MysqlAdapter")
|
53
|
-
ActiveRecord::ConnectionAdapters
|
54
|
-
:
|
55
|
-
)
|
52
|
+
if ActiveRecord::ConnectionAdapters.constants.include?("MysqlAdapter") or ActiveRecord::Base.respond_to?(:jdbcmysql_connection)
|
53
|
+
adapter = ActiveRecord::ConnectionAdapters.const_get(
|
54
|
+
defined?(JRUBY_VERSION) ? :JdbcAdapter : :MysqlAdapter
|
55
|
+
)
|
56
|
+
unless adapter.instance_methods.include?("quote_table_name")
|
57
|
+
adapter.send(:include, ThinkingSphinx::MysqlQuotedTableName)
|
58
|
+
end
|
56
59
|
end
|
57
60
|
|
58
61
|
module ThinkingSphinx
|