summaryrefslogtreecommitdiff
path: root/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index.rb
diff options
context:
space:
mode:
authorhukl <contact@smyck.org>2011-02-10 14:19:00 +0100
committerhukl <contact@smyck.org>2011-02-10 14:19:00 +0100
commit7379daad1c73bd3610ed296436250b417ac3673d (patch)
tree04f722efc678de9d3aa5bf8f1c96e3be33b18bc4 /vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index.rb
parent91633ac4419d839661e35ae8f2efe5c9089cfb67 (diff)
removed thinking_sphinx plugin and replaced it with gem.
also tuned dependencies
Diffstat (limited to 'vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index.rb')
-rw-r--r--vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index.rb423
1 files changed, 0 insertions, 423 deletions
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index.rb
deleted file mode 100644
index 30cfe38..0000000
--- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index.rb
+++ /dev/null
@@ -1,423 +0,0 @@
1require 'thinking_sphinx/index/builder'
2require 'thinking_sphinx/index/faux_column'
3
4module 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_and_facets
52 link!
53
54 source = Riddle::Configuration::SQLSource.new(
55 "#{name}_core_#{index}", adapter.sphinx_identifier
56 )
57
58 set_source_database_settings source
59 set_source_attributes source, offset
60 set_source_sql source, offset
61 set_source_settings source
62
63 source
64 end
65
66 def to_riddle_for_delta(offset, index)
67 add_internal_attributes_and_facets
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, offset
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", "Java::JavaSql::SQLException", "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 if attrib.include_as_association?
204 }.compact.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), true),
242 @model.to_crc32
243 ))
244 else
245 @model.to_crc32.to_s
246 end
247 end
248
249 def add_internal_attributes_and_facets
250 add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key.to_sym
251 add_internal_attribute :class_crc, :integer, crc_column, true
252 add_internal_attribute :subclass_crcs, :multi, subclasses_to_s
253 add_internal_attribute :sphinx_deleted, :integer, "0"
254
255 add_internal_facet :class_crc
256 end
257
258 def add_internal_attribute(name, type, contents, facet = false)
259 return unless attribute_by_alias(name).nil?
260
261 @attributes << Attribute.new(
262 FauxColumn.new(contents),
263 :type => type,
264 :as => name,
265 :facet => facet
266 )
267 end
268
269 def add_internal_facet(name)
270 return unless facet_by_alias(name).nil?
271
272 @model.sphinx_facets << ClassFacet.new(attribute_by_alias(name))
273 end
274
275 def attribute_by_alias(attr_alias)
276 @attributes.detect { |attrib| attrib.alias == attr_alias }
277 end
278
279 def facet_by_alias(name)
280 @model.sphinx_facets.detect { |facet| facet.name == name }
281 end
282
283 def subclasses_to_s
284 "'" + (@model.send(:subclasses).collect { |klass|
285 klass.to_crc32.to_s
286 } << @model.to_crc32.to_s).join(",") + "'"
287 end
288
289 def set_source_database_settings(source)
290 config = @model.connection.instance_variable_get(:@config)
291
292 source.sql_host = config[:host] || "localhost"
293 source.sql_user = config[:username] || config[:user] || ""
294 source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
295 source.sql_db = config[:database]
296 source.sql_port = config[:port]
297 source.sql_sock = config[:socket]
298 end
299
300 def set_source_attributes(source, offset = nil)
301 attributes.each do |attrib|
302 source.send(attrib.type_to_config) << attrib.config_value(offset)
303 end
304 end
305
306 def set_source_sql(source, offset, delta = false)
307 source.sql_query = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
308 source.sql_query_range = to_sql_query_range(:delta => delta)
309 source.sql_query_info = to_sql_query_info(offset)
310
311 source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
312
313 if @options[:group_concat_max_len]
314 source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
315 end
316
317 source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
318 end
319
320 def set_source_settings(source)
321 ThinkingSphinx::Configuration.instance.source_options.each do |key, value|
322 source.send("#{key}=".to_sym, value)
323 end
324
325 @options.each do |key, value|
326 source.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::SourceOptions.include?(key.to_s) && !value.nil?
327 end
328 end
329
330 def sql_query_pre_for_core
331 if self.delta? && !@delta_object.reset_query(@model).blank?
332 [@delta_object.reset_query(@model)]
333 else
334 []
335 end
336 end
337
338 def sql_query_pre_for_delta
339 [""]
340 end
341
342 # Generates the big SQL statement to get the data back for all the fields
343 # and attributes, using all the relevant association joins. If you want
344 # the version filtered for delta values, send through :delta => true in the
345 # options. Won't do much though if the index isn't set up to support a
346 # delta sibling.
347 #
348 # Examples:
349 #
350 # index.to_sql
351 # index.to_sql(:delta => true)
352 #
353 def to_sql(options={})
354 assocs = all_associations
355
356 where_clause = ""
357 if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
358 where_clause << " AND #{@delta_object.clause(@model, options[:delta])}"
359 end
360 unless @conditions.empty?
361 where_clause << " AND " << @conditions.join(" AND ")
362 end
363
364 internal_groupings = []
365 if @model.column_names.include?(@model.inheritance_column)
366 internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
367 end
368
369 unique_id_expr = ThinkingSphinx.unique_id_expression(options[:offset])
370
371 sql = <<-SQL
372SELECT #{ (
373 ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
374 @fields.collect { |field| field.to_select_sql } +
375 @attributes.collect { |attribute| attribute.to_select_sql }
376).compact.join(", ") }
377FROM #{ @model.table_name }
378 #{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
379WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
380 AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
381 #{ where_clause }
382GROUP BY #{ (
383 ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
384 @fields.collect { |field| field.to_group_sql }.compact +
385 @attributes.collect { |attribute| attribute.to_group_sql }.compact +
386 @groupings + internal_groupings
387).join(", ") }
388 SQL
389
390 sql += " ORDER BY NULL" if adapter.sphinx_identifier == "mysql"
391 sql
392 end
393
394 # Simple helper method for the query info SQL - which is a statement that
395 # returns the single row for a corresponding id.
396 #
397 def to_sql_query_info(offset)
398 "SELECT * FROM #{@model.quoted_table_name} WHERE " +
399 " #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
400 end
401
402 # Simple helper method for the query range SQL - which is a statement that
403 # returns minimum and maximum id values. These can be filtered by delta -
404 # so pass in :delta => true to get the delta version of the SQL.
405 #
406 def to_sql_query_range(options={})
407 min_statement = adapter.convert_nulls(
408 "MIN(#{quote_column(@model.primary_key)})", 1
409 )
410 max_statement = adapter.convert_nulls(
411 "MAX(#{quote_column(@model.primary_key)})", 1
412 )
413
414 sql = "SELECT #{min_statement}, #{max_statement} " +
415 "FROM #{@model.quoted_table_name} "
416 if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
417 sql << "WHERE #{@delta_object.clause(@model, options[:delta])}"
418 end
419
420 sql
421 end
422 end
423end