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)
         |