diff options
| author | hukl <contact@smyck.org> | 2011-02-10 14:19:00 +0100 |
|---|---|---|
| committer | hukl <contact@smyck.org> | 2011-02-10 14:19:00 +0100 |
| commit | 7379daad1c73bd3610ed296436250b417ac3673d (patch) | |
| tree | 04f722efc678de9d3aa5bf8f1c96e3be33b18bc4 /vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index.rb | |
| parent | 91633ac4419d839661e35ae8f2efe5c9089cfb67 (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.rb | 423 |
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 @@ | |||
| 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_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 | ||
| 372 | SELECT #{ ( | ||
| 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(", ") } | ||
| 377 | FROM #{ @model.table_name } | ||
| 378 | #{ assocs.collect { |assoc| assoc.to_sql }.join(' ') } | ||
| 379 | WHERE #{@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 } | ||
| 382 | GROUP 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 | ||
| 423 | end | ||
