diff options
Diffstat (limited to 'vendor/plugins/thinking-sphinx/lib/thinking_sphinx')
29 files changed, 0 insertions, 3960 deletions
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record.rb deleted file mode 100644 index 3e85b50..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record.rb +++ /dev/null | |||
| @@ -1,260 +0,0 @@ | |||
| 1 | require 'thinking_sphinx/active_record/delta' | ||
| 2 | require 'thinking_sphinx/active_record/search' | ||
| 3 | require 'thinking_sphinx/active_record/has_many_association' | ||
| 4 | |||
| 5 | module ThinkingSphinx | ||
| 6 | # Core additions to ActiveRecord models - define_index for creating indexes | ||
| 7 | # for models. If you want to interrogate the index objects created for the | ||
| 8 | # model, you can use the class-level accessor :sphinx_indexes. | ||
| 9 | # | ||
| 10 | module ActiveRecord | ||
| 11 | def self.included(base) | ||
| 12 | base.class_eval do | ||
| 13 | class_inheritable_array :sphinx_indexes, :sphinx_facets | ||
| 14 | class << self | ||
| 15 | # Allows creation of indexes for Sphinx. If you don't do this, there | ||
| 16 | # isn't much point trying to search (or using this plugin at all, | ||
| 17 | # really). | ||
| 18 | # | ||
| 19 | # An example or two: | ||
| 20 | # | ||
| 21 | # define_index | ||
| 22 | # indexes :id, :as => :model_id | ||
| 23 | # indexes name | ||
| 24 | # end | ||
| 25 | # | ||
| 26 | # You can also grab fields from associations - multiple levels deep | ||
| 27 | # if necessary. | ||
| 28 | # | ||
| 29 | # define_index do | ||
| 30 | # indexes tags.name, :as => :tag | ||
| 31 | # indexes articles.content | ||
| 32 | # indexes orders.line_items.product.name, :as => :product | ||
| 33 | # end | ||
| 34 | # | ||
| 35 | # And it will automatically concatenate multiple fields: | ||
| 36 | # | ||
| 37 | # define_index do | ||
| 38 | # indexes [author.first_name, author.last_name], :as => :author | ||
| 39 | # end | ||
| 40 | # | ||
| 41 | # The #indexes method is for fields - if you want attributes, use | ||
| 42 | # #has instead. All the same rules apply - but keep in mind that | ||
| 43 | # attributes are for sorting, grouping and filtering, not searching. | ||
| 44 | # | ||
| 45 | # define_index do | ||
| 46 | # # fields ... | ||
| 47 | # | ||
| 48 | # has created_at, updated_at | ||
| 49 | # end | ||
| 50 | # | ||
| 51 | # One last feature is the delta index. This requires the model to | ||
| 52 | # have a boolean field named 'delta', and is enabled as follows: | ||
| 53 | # | ||
| 54 | # define_index do | ||
| 55 | # # fields ... | ||
| 56 | # # attributes ... | ||
| 57 | # | ||
| 58 | # set_property :delta => true | ||
| 59 | # end | ||
| 60 | # | ||
| 61 | # Check out the more detailed documentation for each of these methods | ||
| 62 | # at ThinkingSphinx::Index::Builder. | ||
| 63 | # | ||
| 64 | def define_index(&block) | ||
| 65 | return unless ThinkingSphinx.define_indexes? | ||
| 66 | |||
| 67 | self.sphinx_indexes ||= [] | ||
| 68 | index = Index.new(self, &block) | ||
| 69 | |||
| 70 | self.sphinx_indexes << index | ||
| 71 | unless ThinkingSphinx.indexed_models.include?(self.name) | ||
| 72 | ThinkingSphinx.indexed_models << self.name | ||
| 73 | end | ||
| 74 | |||
| 75 | if index.delta? | ||
| 76 | before_save :toggle_delta | ||
| 77 | after_commit :index_delta | ||
| 78 | end | ||
| 79 | |||
| 80 | after_destroy :toggle_deleted | ||
| 81 | |||
| 82 | index | ||
| 83 | end | ||
| 84 | alias_method :sphinx_index, :define_index | ||
| 85 | |||
| 86 | def sphinx_index_options | ||
| 87 | sphinx_indexes.last.options | ||
| 88 | end | ||
| 89 | |||
| 90 | # Generate a unique CRC value for the model's name, to use to | ||
| 91 | # determine which Sphinx documents belong to which AR records. | ||
| 92 | # | ||
| 93 | # Really only written for internal use - but hey, if it's useful to | ||
| 94 | # you in some other way, awesome. | ||
| 95 | # | ||
| 96 | def to_crc32 | ||
| 97 | self.name.to_crc32 | ||
| 98 | end | ||
| 99 | |||
| 100 | def to_crc32s | ||
| 101 | (subclasses << self).collect { |klass| klass.to_crc32 } | ||
| 102 | end | ||
| 103 | |||
| 104 | def source_of_sphinx_index | ||
| 105 | possible_models = self.sphinx_indexes.collect { |index| index.model } | ||
| 106 | return self if possible_models.include?(self) | ||
| 107 | |||
| 108 | parent = self.superclass | ||
| 109 | while !possible_models.include?(parent) && parent != ::ActiveRecord::Base | ||
| 110 | parent = parent.superclass | ||
| 111 | end | ||
| 112 | |||
| 113 | return parent | ||
| 114 | end | ||
| 115 | |||
| 116 | def to_riddle(offset) | ||
| 117 | sphinx_database_adapter.setup | ||
| 118 | |||
| 119 | indexes = [to_riddle_for_core(offset)] | ||
| 120 | indexes << to_riddle_for_delta(offset) if sphinx_delta? | ||
| 121 | indexes << to_riddle_for_distributed | ||
| 122 | end | ||
| 123 | |||
| 124 | def sphinx_database_adapter | ||
| 125 | @sphinx_database_adapter ||= | ||
| 126 | ThinkingSphinx::AbstractAdapter.detect(self) | ||
| 127 | end | ||
| 128 | |||
| 129 | private | ||
| 130 | |||
| 131 | def sphinx_name | ||
| 132 | self.name.underscore.tr(':/\\', '_') | ||
| 133 | end | ||
| 134 | |||
| 135 | def sphinx_delta? | ||
| 136 | self.sphinx_indexes.any? { |index| index.delta? } | ||
| 137 | end | ||
| 138 | |||
| 139 | def to_riddle_for_core(offset) | ||
| 140 | index = Riddle::Configuration::Index.new("#{sphinx_name}_core") | ||
| 141 | index.path = File.join( | ||
| 142 | ThinkingSphinx::Configuration.instance.searchd_file_path, index.name | ||
| 143 | ) | ||
| 144 | |||
| 145 | set_configuration_options_for_indexes index | ||
| 146 | set_field_settings_for_indexes index | ||
| 147 | |||
| 148 | self.sphinx_indexes.select { |ts_index| | ||
| 149 | ts_index.model == self | ||
| 150 | }.each_with_index do |ts_index, i| | ||
| 151 | index.sources << ts_index.to_riddle_for_core(offset, i) | ||
| 152 | end | ||
| 153 | |||
| 154 | index | ||
| 155 | end | ||
| 156 | |||
| 157 | def to_riddle_for_delta(offset) | ||
| 158 | index = Riddle::Configuration::Index.new("#{sphinx_name}_delta") | ||
| 159 | index.parent = "#{sphinx_name}_core" | ||
| 160 | index.path = File.join(ThinkingSphinx::Configuration.instance.searchd_file_path, index.name) | ||
| 161 | |||
| 162 | self.sphinx_indexes.each_with_index do |ts_index, i| | ||
| 163 | index.sources << ts_index.to_riddle_for_delta(offset, i) if ts_index.delta? | ||
| 164 | end | ||
| 165 | |||
| 166 | index | ||
| 167 | end | ||
| 168 | |||
| 169 | def to_riddle_for_distributed | ||
| 170 | index = Riddle::Configuration::DistributedIndex.new(sphinx_name) | ||
| 171 | index.local_indexes << "#{sphinx_name}_core" | ||
| 172 | index.local_indexes.unshift "#{sphinx_name}_delta" if sphinx_delta? | ||
| 173 | index | ||
| 174 | end | ||
| 175 | |||
| 176 | def set_configuration_options_for_indexes(index) | ||
| 177 | ThinkingSphinx::Configuration.instance.index_options.each do |key, value| | ||
| 178 | index.send("#{key}=".to_sym, value) | ||
| 179 | end | ||
| 180 | |||
| 181 | self.sphinx_indexes.each do |ts_index| | ||
| 182 | ts_index.options.each do |key, value| | ||
| 183 | index.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) && !value.nil? | ||
| 184 | end | ||
| 185 | end | ||
| 186 | end | ||
| 187 | |||
| 188 | def set_field_settings_for_indexes(index) | ||
| 189 | field_names = lambda { |field| field.unique_name.to_s } | ||
| 190 | |||
| 191 | self.sphinx_indexes.each do |ts_index| | ||
| 192 | index.prefix_field_names += ts_index.prefix_fields.collect(&field_names) | ||
| 193 | index.infix_field_names += ts_index.infix_fields.collect(&field_names) | ||
| 194 | end | ||
| 195 | end | ||
| 196 | end | ||
| 197 | end | ||
| 198 | |||
| 199 | base.send(:include, ThinkingSphinx::ActiveRecord::Delta) | ||
| 200 | base.send(:include, ThinkingSphinx::ActiveRecord::Search) | ||
| 201 | |||
| 202 | ::ActiveRecord::Associations::HasManyAssociation.send( | ||
| 203 | :include, ThinkingSphinx::ActiveRecord::HasManyAssociation | ||
| 204 | ) | ||
| 205 | ::ActiveRecord::Associations::HasManyThroughAssociation.send( | ||
| 206 | :include, ThinkingSphinx::ActiveRecord::HasManyAssociation | ||
| 207 | ) | ||
| 208 | end | ||
| 209 | |||
| 210 | def in_index?(suffix) | ||
| 211 | self.class.search_for_id self.sphinx_document_id, sphinx_index_name(suffix) | ||
| 212 | end | ||
| 213 | |||
| 214 | def in_core_index? | ||
| 215 | in_index? "core" | ||
| 216 | end | ||
| 217 | |||
| 218 | def in_delta_index? | ||
| 219 | in_index? "delta" | ||
| 220 | end | ||
| 221 | |||
| 222 | def in_both_indexes? | ||
| 223 | in_core_index? && in_delta_index? | ||
| 224 | end | ||
| 225 | |||
| 226 | def toggle_deleted | ||
| 227 | return unless ThinkingSphinx.updates_enabled? && ThinkingSphinx.sphinx_running? | ||
| 228 | |||
| 229 | config = ThinkingSphinx::Configuration.instance | ||
| 230 | client = Riddle::Client.new config.address, config.port | ||
| 231 | |||
| 232 | client.update( | ||
| 233 | "#{self.class.sphinx_indexes.first.name}_core", | ||
| 234 | ['sphinx_deleted'], | ||
| 235 | {self.sphinx_document_id => 1} | ||
| 236 | ) if self.in_core_index? | ||
| 237 | |||
| 238 | client.update( | ||
| 239 | "#{self.class.sphinx_indexes.first.name}_delta", | ||
| 240 | ['sphinx_deleted'], | ||
| 241 | {self.sphinx_document_id => 1} | ||
| 242 | ) if ThinkingSphinx.deltas_enabled? && | ||
| 243 | self.class.sphinx_indexes.any? { |index| index.delta? } && | ||
| 244 | self.toggled_delta? | ||
| 245 | rescue ::ThinkingSphinx::ConnectionError | ||
| 246 | # nothing | ||
| 247 | end | ||
| 248 | |||
| 249 | def sphinx_document_id | ||
| 250 | (self.id * ThinkingSphinx.indexed_models.size) + | ||
| 251 | ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name) | ||
| 252 | end | ||
| 253 | |||
| 254 | private | ||
| 255 | |||
| 256 | def sphinx_index_name(suffix) | ||
| 257 | "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}" | ||
| 258 | end | ||
| 259 | end | ||
| 260 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record/delta.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record/delta.rb deleted file mode 100644 index f21aba2..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record/delta.rb +++ /dev/null | |||
| @@ -1,78 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | module ActiveRecord | ||
| 3 | # This module contains all the delta-related code for models. There isn't | ||
| 4 | # really anything you need to call manually in here - except perhaps | ||
| 5 | # index_delta, but not sure what reason why. | ||
| 6 | # | ||
| 7 | module Delta | ||
| 8 | # Code for after_commit callback is written by Eli Miller: | ||
| 9 | # http://elimiller.blogspot.com/2007/06/proper-cache-expiry-with-aftercommit.html | ||
| 10 | # with slight modification from Joost Hietbrink. | ||
| 11 | # | ||
| 12 | def self.included(base) | ||
| 13 | base.class_eval do | ||
| 14 | class << self | ||
| 15 | # Temporarily disable delta indexing inside a block, then perform a single | ||
| 16 | # rebuild of index at the end. | ||
| 17 | # | ||
| 18 | # Useful when performing updates to batches of models to prevent | ||
| 19 | # the delta index being rebuilt after each individual update. | ||
| 20 | # | ||
| 21 | # In the following example, the delta index will only be rebuilt once, | ||
| 22 | # not 10 times. | ||
| 23 | # | ||
| 24 | # SomeModel.suspended_delta do | ||
| 25 | # 10.times do | ||
| 26 | # SomeModel.create( ... ) | ||
| 27 | # end | ||
| 28 | # end | ||
| 29 | # | ||
| 30 | def suspended_delta(reindex_after = true, &block) | ||
| 31 | original_setting = ThinkingSphinx.deltas_enabled? | ||
| 32 | ThinkingSphinx.deltas_enabled = false | ||
| 33 | begin | ||
| 34 | yield | ||
| 35 | ensure | ||
| 36 | ThinkingSphinx.deltas_enabled = original_setting | ||
| 37 | self.index_delta if reindex_after | ||
| 38 | end | ||
| 39 | end | ||
| 40 | |||
| 41 | # Build the delta index for the related model. This won't be called | ||
| 42 | # if running in the test environment. | ||
| 43 | # | ||
| 44 | def index_delta(instance = nil) | ||
| 45 | delta_object.index(self, instance) | ||
| 46 | end | ||
| 47 | |||
| 48 | def delta_object | ||
| 49 | self.sphinx_indexes.first.delta_object | ||
| 50 | end | ||
| 51 | end | ||
| 52 | |||
| 53 | def toggled_delta? | ||
| 54 | self.class.delta_object.toggled(self) | ||
| 55 | end | ||
| 56 | |||
| 57 | private | ||
| 58 | |||
| 59 | # Set the delta value for the model to be true. | ||
| 60 | def toggle_delta | ||
| 61 | self.class.delta_object.toggle(self) if should_toggle_delta? | ||
| 62 | end | ||
| 63 | |||
| 64 | # Build the delta index for the related model. This won't be called | ||
| 65 | # if running in the test environment. | ||
| 66 | # | ||
| 67 | def index_delta | ||
| 68 | self.class.index_delta(self) if self.class.delta_object.toggled(self) | ||
| 69 | end | ||
| 70 | |||
| 71 | def should_toggle_delta? | ||
| 72 | !self.respond_to?(:changed?) || self.changed? || self.new_record? | ||
| 73 | end | ||
| 74 | end | ||
| 75 | end | ||
| 76 | end | ||
| 77 | end | ||
| 78 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record/has_many_association.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record/has_many_association.rb deleted file mode 100644 index 44b25c0..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record/has_many_association.rb +++ /dev/null | |||
| @@ -1,29 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | module ActiveRecord | ||
| 3 | module HasManyAssociation | ||
| 4 | def search(*args) | ||
| 5 | foreign_key = @reflection.primary_key_name | ||
| 6 | stack = [@reflection.options[:through]].compact | ||
| 7 | |||
| 8 | attribute = nil | ||
| 9 | (@reflection.klass.sphinx_indexes || []).each do |index| | ||
| 10 | attribute = index.attributes.detect { |attrib| | ||
| 11 | attrib.columns.length == 1 && | ||
| 12 | attrib.columns.first.__name == foreign_key.to_sym && | ||
| 13 | attrib.columns.first.__stack == stack | ||
| 14 | } | ||
| 15 | break if attribute | ||
| 16 | end | ||
| 17 | |||
| 18 | raise "Missing Attribute for Foreign Key #{foreign_key}" unless attribute | ||
| 19 | |||
| 20 | options = args.extract_options! | ||
| 21 | options[:with] ||= {} | ||
| 22 | options[:with][attribute.unique_name] = @owner.id | ||
| 23 | |||
| 24 | args << options | ||
| 25 | @reflection.klass.search(*args) | ||
| 26 | end | ||
| 27 | end | ||
| 28 | end | ||
| 29 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record/search.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record/search.rb deleted file mode 100644 index fc3f2b4..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/active_record/search.rb +++ /dev/null | |||
| @@ -1,57 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | module ActiveRecord | ||
| 3 | # This module covers the specific model searches - but the syntax is | ||
| 4 | # exactly the same as the core Search class - so use that as your refence | ||
| 5 | # point. | ||
| 6 | # | ||
| 7 | module Search | ||
| 8 | def self.included(base) | ||
| 9 | base.class_eval do | ||
| 10 | class << self | ||
| 11 | # Searches for results that match the parameters provided. Will only | ||
| 12 | # return the ids for the matching objects. See | ||
| 13 | # ThinkingSphinx::Search#search for syntax examples. | ||
| 14 | # | ||
| 15 | def search_for_ids(*args) | ||
| 16 | options = args.extract_options! | ||
| 17 | options[:class] = self | ||
| 18 | args << options | ||
| 19 | ThinkingSphinx::Search.search_for_ids(*args) | ||
| 20 | end | ||
| 21 | |||
| 22 | # Searches for results limited to a single model. See | ||
| 23 | # ThinkingSphinx::Search#search for syntax examples. | ||
| 24 | # | ||
| 25 | def search(*args) | ||
| 26 | options = args.extract_options! | ||
| 27 | options[:class] = self | ||
| 28 | args << options | ||
| 29 | ThinkingSphinx::Search.search(*args) | ||
| 30 | end | ||
| 31 | |||
| 32 | def search_count(*args) | ||
| 33 | options = args.extract_options! | ||
| 34 | options[:class] = self | ||
| 35 | args << options | ||
| 36 | ThinkingSphinx::Search.count(*args) | ||
| 37 | end | ||
| 38 | |||
| 39 | def search_for_id(*args) | ||
| 40 | options = args.extract_options! | ||
| 41 | options[:class] = self | ||
| 42 | args << options | ||
| 43 | ThinkingSphinx::Search.search_for_id(*args) | ||
| 44 | end | ||
| 45 | |||
| 46 | def facets(*args) | ||
| 47 | options = args.extract_options! | ||
| 48 | options[:class] = self | ||
| 49 | args << options | ||
| 50 | ThinkingSphinx::Search.facets(*args) | ||
| 51 | end | ||
| 52 | end | ||
| 53 | end | ||
| 54 | end | ||
| 55 | end | ||
| 56 | end | ||
| 57 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/adapters/abstract_adapter.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/adapters/abstract_adapter.rb deleted file mode 100644 index b68b75e..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/adapters/abstract_adapter.rb +++ /dev/null | |||
| @@ -1,42 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | class AbstractAdapter | ||
| 3 | def initialize(model) | ||
| 4 | @model = model | ||
| 5 | end | ||
| 6 | |||
| 7 | def setup | ||
| 8 | # Deliberately blank - subclasses should do something though. Well, if | ||
| 9 | # they need to. | ||
| 10 | end | ||
| 11 | |||
| 12 | def self.detect(model) | ||
| 13 | case model.connection.class.name | ||
| 14 | when "ActiveRecord::ConnectionAdapters::MysqlAdapter", | ||
| 15 | "ActiveRecord::ConnectionAdapters::MysqlplusAdapter" | ||
| 16 | ThinkingSphinx::MysqlAdapter.new model | ||
| 17 | when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter" | ||
| 18 | ThinkingSphinx::PostgreSQLAdapter.new model | ||
| 19 | when "ActiveRecord::ConnectionAdapters::JdbcAdapter" | ||
| 20 | if model.connection.config[:adapter] == "jdbcmysql" | ||
| 21 | ThinkingSphinx::MysqlAdapter.new model | ||
| 22 | elsif model.connection.config[:adapter] == "jdbcpostgresql" | ||
| 23 | ThinkingSphinx::PostgreSQLAdapter.new model | ||
| 24 | else | ||
| 25 | raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL" | ||
| 26 | end | ||
| 27 | else | ||
| 28 | raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{model.connection.class.name}" | ||
| 29 | end | ||
| 30 | end | ||
| 31 | |||
| 32 | def quote_with_table(column) | ||
| 33 | "#{@model.quoted_table_name}.#{@model.connection.quote_column_name(column)}" | ||
| 34 | end | ||
| 35 | |||
| 36 | protected | ||
| 37 | |||
| 38 | def connection | ||
| 39 | @connection ||= @model.connection | ||
| 40 | end | ||
| 41 | end | ||
| 42 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/adapters/mysql_adapter.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/adapters/mysql_adapter.rb deleted file mode 100644 index 597d4b6..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/adapters/mysql_adapter.rb +++ /dev/null | |||
| @@ -1,54 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | class MysqlAdapter < AbstractAdapter | ||
| 3 | def setup | ||
| 4 | # Does MySQL actually need to do anything? | ||
| 5 | end | ||
| 6 | |||
| 7 | def sphinx_identifier | ||
| 8 | "mysql" | ||
| 9 | end | ||
| 10 | |||
| 11 | def concatenate(clause, separator = ' ') | ||
| 12 | "CONCAT_WS('#{separator}', #{clause})" | ||
| 13 | end | ||
| 14 | |||
| 15 | def group_concatenate(clause, separator = ' ') | ||
| 16 | "GROUP_CONCAT(DISTINCT #{clause} SEPARATOR '#{separator}')" | ||
| 17 | end | ||
| 18 | |||
| 19 | def cast_to_string(clause) | ||
| 20 | "CAST(#{clause} AS CHAR)" | ||
| 21 | end | ||
| 22 | |||
| 23 | def cast_to_datetime(clause) | ||
| 24 | "UNIX_TIMESTAMP(#{clause})" | ||
| 25 | end | ||
| 26 | |||
| 27 | def cast_to_unsigned(clause) | ||
| 28 | "CAST(#{clause} AS UNSIGNED)" | ||
| 29 | end | ||
| 30 | |||
| 31 | def convert_nulls(clause, default = '') | ||
| 32 | default = "'#{default}'" if default.is_a?(String) | ||
| 33 | |||
| 34 | "IFNULL(#{clause}, #{default})" | ||
| 35 | end | ||
| 36 | |||
| 37 | def boolean(value) | ||
| 38 | value ? 1 : 0 | ||
| 39 | end | ||
| 40 | |||
| 41 | def crc(clause, blank_to_null = false) | ||
| 42 | clause = "NULLIF(#{clause},'')" if blank_to_null | ||
| 43 | "CRC32(#{clause})" | ||
| 44 | end | ||
| 45 | |||
| 46 | def utf8_query_pre | ||
| 47 | "SET NAMES utf8" | ||
| 48 | end | ||
| 49 | |||
| 50 | def time_difference(diff) | ||
| 51 | "DATE_SUB(NOW(), INTERVAL #{diff} SECOND)" | ||
| 52 | end | ||
| 53 | end | ||
| 54 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/adapters/postgresql_adapter.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/adapters/postgresql_adapter.rb deleted file mode 100644 index 625971d..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/adapters/postgresql_adapter.rb +++ /dev/null | |||
| @@ -1,130 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | class PostgreSQLAdapter < AbstractAdapter | ||
| 3 | def setup | ||
| 4 | create_array_accum_function | ||
| 5 | create_crc32_function | ||
| 6 | end | ||
| 7 | |||
| 8 | def sphinx_identifier | ||
| 9 | "pgsql" | ||
| 10 | end | ||
| 11 | |||
| 12 | def concatenate(clause, separator = ' ') | ||
| 13 | clause.split(', ').collect { |field| | ||
| 14 | "COALESCE(CAST(#{field} as varchar), '')" | ||
| 15 | }.join(" || '#{separator}' || ") | ||
| 16 | end | ||
| 17 | |||
| 18 | def group_concatenate(clause, separator = ' ') | ||
| 19 | "array_to_string(array_accum(#{clause}), '#{separator}')" | ||
| 20 | end | ||
| 21 | |||
| 22 | def cast_to_string(clause) | ||
| 23 | clause | ||
| 24 | end | ||
| 25 | |||
| 26 | def cast_to_datetime(clause) | ||
| 27 | "cast(extract(epoch from #{clause}) as int)" | ||
| 28 | end | ||
| 29 | |||
| 30 | def cast_to_unsigned(clause) | ||
| 31 | clause | ||
| 32 | end | ||
| 33 | |||
| 34 | def convert_nulls(clause, default = '') | ||
| 35 | default = "'#{default}'" if default.is_a?(String) | ||
| 36 | |||
| 37 | "COALESCE(#{clause}, #{default})" | ||
| 38 | end | ||
| 39 | |||
| 40 | def boolean(value) | ||
| 41 | value ? 'TRUE' : 'FALSE' | ||
| 42 | end | ||
| 43 | |||
| 44 | def crc(clause, blank_to_null = false) | ||
| 45 | clause = "NULLIF(#{clause},'')" if blank_to_null | ||
| 46 | "crc32(#{clause})" | ||
| 47 | end | ||
| 48 | |||
| 49 | def utf8_query_pre | ||
| 50 | nil | ||
| 51 | end | ||
| 52 | |||
| 53 | def time_difference(diff) | ||
| 54 | "current_timestamp - interval '#{diff} seconds'" | ||
| 55 | end | ||
| 56 | |||
| 57 | private | ||
| 58 | |||
| 59 | def execute(command, output_error = false) | ||
| 60 | connection.execute "begin" | ||
| 61 | connection.execute "savepoint ts" | ||
| 62 | begin | ||
| 63 | connection.execute command | ||
| 64 | rescue StandardError => err | ||
| 65 | puts err if output_error | ||
| 66 | connection.execute "rollback to savepoint ts" | ||
| 67 | end | ||
| 68 | connection.execute "release savepoint ts" | ||
| 69 | connection.execute "commit" | ||
| 70 | end | ||
| 71 | |||
| 72 | def create_array_accum_function | ||
| 73 | if connection.raw_connection.respond_to?(:server_version) && connection.raw_connection.server_version > 80200 | ||
| 74 | execute <<-SQL | ||
| 75 | CREATE AGGREGATE array_accum (anyelement) | ||
| 76 | ( | ||
| 77 | sfunc = array_append, | ||
| 78 | stype = anyarray, | ||
| 79 | initcond = '{}' | ||
| 80 | ); | ||
| 81 | SQL | ||
| 82 | else | ||
| 83 | execute <<-SQL | ||
| 84 | CREATE AGGREGATE array_accum | ||
| 85 | ( | ||
| 86 | basetype = anyelement, | ||
| 87 | sfunc = array_append, | ||
| 88 | stype = anyarray, | ||
| 89 | initcond = '{}' | ||
| 90 | ); | ||
| 91 | SQL | ||
| 92 | end | ||
| 93 | end | ||
| 94 | |||
| 95 | def create_crc32_function | ||
| 96 | execute "CREATE LANGUAGE 'plpgsql';" | ||
| 97 | function = <<-SQL | ||
| 98 | CREATE OR REPLACE FUNCTION crc32(word text) | ||
| 99 | RETURNS bigint AS $$ | ||
| 100 | DECLARE tmp bigint; | ||
| 101 | DECLARE i int; | ||
| 102 | DECLARE j int; | ||
| 103 | DECLARE word_array bytea; | ||
| 104 | BEGIN | ||
| 105 | i = 0; | ||
| 106 | tmp = 4294967295; | ||
| 107 | word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape'); | ||
| 108 | LOOP | ||
| 109 | tmp = (tmp # get_byte(word_array, i))::bigint; | ||
| 110 | i = i + 1; | ||
| 111 | j = 0; | ||
| 112 | LOOP | ||
| 113 | tmp = ((tmp >> 1) # (3988292384 * (tmp & 1)))::bigint; | ||
| 114 | j = j + 1; | ||
| 115 | IF j >= 8 THEN | ||
| 116 | EXIT; | ||
| 117 | END IF; | ||
| 118 | END LOOP; | ||
| 119 | IF i >= char_length(word) THEN | ||
| 120 | EXIT; | ||
| 121 | END IF; | ||
| 122 | END LOOP; | ||
| 123 | return (tmp # 4294967295); | ||
| 124 | END | ||
| 125 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; | ||
| 126 | SQL | ||
| 127 | execute function, true | ||
| 128 | end | ||
| 129 | end | ||
| 130 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/association.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/association.rb deleted file mode 100644 index b057035..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/association.rb +++ /dev/null | |||
| @@ -1,161 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | # Association tracks a specific reflection and join to reference data that | ||
| 3 | # isn't in the base model. Very much an internal class for Thinking Sphinx - | ||
| 4 | # perhaps because I feel it's not as strong (or simple) as most of the rest. | ||
| 5 | # | ||
| 6 | class Association | ||
| 7 | attr_accessor :parent, :reflection, :join | ||
| 8 | |||
| 9 | # Create a new association by passing in the parent association, and the | ||
| 10 | # corresponding reflection instance. If there is no parent, pass in nil. | ||
| 11 | # | ||
| 12 | # top = Association.new nil, top_reflection | ||
| 13 | # child = Association.new top, child_reflection | ||
| 14 | # | ||
| 15 | def initialize(parent, reflection) | ||
| 16 | @parent, @reflection = parent, reflection | ||
| 17 | @children = {} | ||
| 18 | end | ||
| 19 | |||
| 20 | # Get the children associations for a given association name. The only time | ||
| 21 | # that there'll actually be more than one association is when the | ||
| 22 | # relationship is polymorphic. To keep things simple though, it will always | ||
| 23 | # be an Array that gets returned (an empty one if no matches). | ||
| 24 | # | ||
| 25 | # # where pages is an association on the class tied to the reflection. | ||
| 26 | # association.children(:pages) | ||
| 27 | # | ||
| 28 | def children(assoc) | ||
| 29 | @children[assoc] ||= Association.children(@reflection.klass, assoc, self) | ||
| 30 | end | ||
| 31 | |||
| 32 | # Get the children associations for a given class, association name and | ||
| 33 | # parent association. Much like the instance method of the same name, it | ||
| 34 | # will return an empty array if no associations have the name, and only | ||
| 35 | # have multiple association instances if the underlying relationship is | ||
| 36 | # polymorphic. | ||
| 37 | # | ||
| 38 | # Association.children(User, :pages, user_association) | ||
| 39 | # | ||
| 40 | def self.children(klass, assoc, parent=nil) | ||
| 41 | ref = klass.reflect_on_association(assoc) | ||
| 42 | |||
| 43 | return [] if ref.nil? | ||
| 44 | return [Association.new(parent, ref)] unless ref.options[:polymorphic] | ||
| 45 | |||
| 46 | # association is polymorphic - create associations for each | ||
| 47 | # non-polymorphic reflection. | ||
| 48 | polymorphic_classes(ref).collect { |klass| | ||
| 49 | Association.new parent, ::ActiveRecord::Reflection::AssociationReflection.new( | ||
| 50 | ref.macro, | ||
| 51 | "#{ref.name}_#{klass.name}".to_sym, | ||
| 52 | casted_options(klass, ref), | ||
| 53 | ref.active_record | ||
| 54 | ) | ||
| 55 | } | ||
| 56 | end | ||
| 57 | |||
| 58 | # Link up the join for this model from a base join - and set parent | ||
| 59 | # associations' joins recursively. | ||
| 60 | # | ||
| 61 | def join_to(base_join) | ||
| 62 | parent.join_to(base_join) if parent && parent.join.nil? | ||
| 63 | |||
| 64 | @join ||= ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.new( | ||
| 65 | @reflection, base_join, parent ? parent.join : base_join.joins.first | ||
| 66 | ) | ||
| 67 | end | ||
| 68 | |||
| 69 | # Returns the association's join SQL statements - and it replaces | ||
| 70 | # ::ts_join_alias:: with the aliased table name so the generated reflection | ||
| 71 | # join conditions avoid column name collisions. | ||
| 72 | # | ||
| 73 | def to_sql | ||
| 74 | @join.association_join.gsub(/::ts_join_alias::/, | ||
| 75 | "#{@reflection.klass.connection.quote_table_name(@join.parent.aliased_table_name)}" | ||
| 76 | ) | ||
| 77 | end | ||
| 78 | |||
| 79 | # Returns true if the association - or a parent - is a has_many or | ||
| 80 | # has_and_belongs_to_many. | ||
| 81 | # | ||
| 82 | def is_many? | ||
| 83 | case @reflection.macro | ||
| 84 | when :has_many, :has_and_belongs_to_many | ||
| 85 | true | ||
| 86 | else | ||
| 87 | @parent ? @parent.is_many? : false | ||
| 88 | end | ||
| 89 | end | ||
| 90 | |||
| 91 | # Returns an array of all the associations that lead to this one - starting | ||
| 92 | # with the top level all the way to the current association object. | ||
| 93 | # | ||
| 94 | def ancestors | ||
| 95 | (parent ? parent.ancestors : []) << self | ||
| 96 | end | ||
| 97 | |||
| 98 | def has_column?(column) | ||
| 99 | @reflection.klass.column_names.include?(column.to_s) | ||
| 100 | end | ||
| 101 | |||
| 102 | def primary_key_from_reflection | ||
| 103 | if @reflection.options[:through] | ||
| 104 | @reflection.source_reflection.options[:foreign_key] || | ||
| 105 | @reflection.source_reflection.primary_key_name | ||
| 106 | else | ||
| 107 | nil | ||
| 108 | end | ||
| 109 | end | ||
| 110 | |||
| 111 | def table | ||
| 112 | if @reflection.options[:through] | ||
| 113 | @join.aliased_join_table_name | ||
| 114 | else | ||
| 115 | @join.aliased_table_name | ||
| 116 | end | ||
| 117 | end | ||
| 118 | |||
| 119 | private | ||
| 120 | |||
| 121 | # Returns all the objects that could be currently instantiated from a | ||
| 122 | # polymorphic association. This is pretty damn fast if there's an index on | ||
| 123 | # the foreign type column - but if there isn't, it can take a while if you | ||
| 124 | # have a lot of data. | ||
| 125 | # | ||
| 126 | def self.polymorphic_classes(ref) | ||
| 127 | ref.active_record.connection.select_all( | ||
| 128 | "SELECT DISTINCT #{ref.options[:foreign_type]} " + | ||
| 129 | "FROM #{ref.active_record.table_name} " + | ||
| 130 | "WHERE #{ref.options[:foreign_type]} IS NOT NULL" | ||
| 131 | ).collect { |row| | ||
| 132 | row[ref.options[:foreign_type]].constantize | ||
| 133 | } | ||
| 134 | end | ||
| 135 | |||
| 136 | # Returns a new set of options for an association that mimics an existing | ||
| 137 | # polymorphic relationship for a specific class. It adds a condition to | ||
| 138 | # filter by the appropriate object. | ||
| 139 | # | ||
| 140 | def self.casted_options(klass, ref) | ||
| 141 | options = ref.options.clone | ||
| 142 | options[:polymorphic] = nil | ||
| 143 | options[:class_name] = klass.name | ||
| 144 | options[:foreign_key] ||= "#{ref.name}_id" | ||
| 145 | |||
| 146 | quoted_foreign_type = klass.connection.quote_column_name ref.options[:foreign_type] | ||
| 147 | case options[:conditions] | ||
| 148 | when nil | ||
| 149 | options[:conditions] = "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'" | ||
| 150 | when Array | ||
| 151 | options[:conditions] << "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'" | ||
| 152 | when Hash | ||
| 153 | options[:conditions].merge!(ref.options[:foreign_type] => klass.name) | ||
| 154 | else | ||
| 155 | options[:conditions] << " AND ::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'" | ||
| 156 | end | ||
| 157 | |||
| 158 | options | ||
| 159 | end | ||
| 160 | end | ||
| 161 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/attribute.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/attribute.rb deleted file mode 100644 index 1d45b2e..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/attribute.rb +++ /dev/null | |||
| @@ -1,358 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | # Attributes - eternally useful when it comes to filtering, sorting or | ||
| 3 | # grouping. This class isn't really useful to you unless you're hacking | ||
| 4 | # around with the internals of Thinking Sphinx - but hey, don't let that | ||
| 5 | # stop you. | ||
| 6 | # | ||
| 7 | # One key thing to remember - if you're using the attribute manually to | ||
| 8 | # generate SQL statements, you'll need to set the base model, and all the | ||
| 9 | # associations. Which can get messy. Use Index.link!, it really helps. | ||
| 10 | # | ||
| 11 | class Attribute | ||
| 12 | attr_accessor :alias, :columns, :associations, :model, :faceted, :source | ||
| 13 | |||
| 14 | # To create a new attribute, you'll need to pass in either a single Column | ||
| 15 | # or an array of them, and some (optional) options. | ||
| 16 | # | ||
| 17 | # Valid options are: | ||
| 18 | # - :as => :alias_name | ||
| 19 | # - :type => :attribute_type | ||
| 20 | # - :source => :field, :query, :ranged_query | ||
| 21 | # | ||
| 22 | # Alias is only required in three circumstances: when there's | ||
| 23 | # another attribute or field with the same name, when the column name is | ||
| 24 | # 'id', or when there's more than one column. | ||
| 25 | # | ||
| 26 | # Type is not required, unless you want to force a column to be a certain | ||
| 27 | # type (but keep in mind the value will not be CASTed in the SQL | ||
| 28 | # statements). The only time you really need to use this is when the type | ||
| 29 | # can't be figured out by the column - ie: when not actually using a | ||
| 30 | # database column as your source. | ||
| 31 | # | ||
| 32 | # Source is only used for multi-value attributes (MVA). By default this will | ||
| 33 | # use a left-join and a group_concat to obtain the values. For better performance | ||
| 34 | # during indexing it can be beneficial to let Sphinx use a separate query to retrieve | ||
| 35 | # all document,value-pairs. | ||
| 36 | # Either :query or :ranged_query will enable this feature, where :ranged_query will cause | ||
| 37 | # the query to be executed incremental. | ||
| 38 | # | ||
| 39 | # Example usage: | ||
| 40 | # | ||
| 41 | # Attribute.new( | ||
| 42 | # Column.new(:created_at) | ||
| 43 | # ) | ||
| 44 | # | ||
| 45 | # Attribute.new( | ||
| 46 | # Column.new(:posts, :id), | ||
| 47 | # :as => :post_ids | ||
| 48 | # ) | ||
| 49 | # | ||
| 50 | # Attribute.new( | ||
| 51 | # Column.new(:posts, :id), | ||
| 52 | # :as => :post_ids, | ||
| 53 | # :source => :ranged_query | ||
| 54 | # ) | ||
| 55 | # | ||
| 56 | # Attribute.new( | ||
| 57 | # [Column.new(:pages, :id), Column.new(:articles, :id)], | ||
| 58 | # :as => :content_ids | ||
| 59 | # ) | ||
| 60 | # | ||
| 61 | # Attribute.new( | ||
| 62 | # Column.new("NOW()"), | ||
| 63 | # :as => :indexed_at, | ||
| 64 | # :type => :datetime | ||
| 65 | # ) | ||
| 66 | # | ||
| 67 | # If you're creating attributes for latitude and longitude, don't forget | ||
| 68 | # that Sphinx expects these values to be in radians. | ||
| 69 | # | ||
| 70 | def initialize(columns, options = {}) | ||
| 71 | @columns = Array(columns) | ||
| 72 | @associations = {} | ||
| 73 | |||
| 74 | raise "Cannot define a field 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) } | ||
| 75 | |||
| 76 | @alias = options[:as] | ||
| 77 | @type = options[:type] | ||
| 78 | @faceted = options[:facet] | ||
| 79 | @source = options[:source] | ||
| 80 | @crc = options[:crc] | ||
| 81 | |||
| 82 | @type ||= :multi unless @source.nil? | ||
| 83 | @type = :integer if @type == :string && @crc | ||
| 84 | end | ||
| 85 | |||
| 86 | # Get the part of the SELECT clause related to this attribute. Don't forget | ||
| 87 | # to set your model and associations first though. | ||
| 88 | # | ||
| 89 | # This will concatenate strings and arrays of integers, and convert | ||
| 90 | # datetimes to timestamps, as needed. | ||
| 91 | # | ||
| 92 | def to_select_sql | ||
| 93 | return nil unless include_as_association? | ||
| 94 | |||
| 95 | clause = @columns.collect { |column| | ||
| 96 | column_with_prefix(column) | ||
| 97 | }.join(', ') | ||
| 98 | |||
| 99 | separator = all_ints? ? ',' : ' ' | ||
| 100 | |||
| 101 | clause = adapter.concatenate(clause, separator) if concat_ws? | ||
| 102 | clause = adapter.group_concatenate(clause, separator) if is_many? | ||
| 103 | clause = adapter.cast_to_datetime(clause) if type == :datetime | ||
| 104 | clause = adapter.convert_nulls(clause) if type == :string | ||
| 105 | clause = adapter.crc(clause) if @crc | ||
| 106 | |||
| 107 | "#{clause} AS #{quote_column(unique_name)}" | ||
| 108 | end | ||
| 109 | |||
| 110 | # Get the part of the GROUP BY clause related to this attribute - if one is | ||
| 111 | # needed. If not, all you'll get back is nil. The latter will happen if | ||
| 112 | # there isn't actually a real column to get data from, or if there's | ||
| 113 | # multiple data values (read: a has_many or has_and_belongs_to_many | ||
| 114 | # association). | ||
| 115 | # | ||
| 116 | def to_group_sql | ||
| 117 | case | ||
| 118 | when is_many?, is_string?, ThinkingSphinx.use_group_by_shortcut? | ||
| 119 | nil | ||
| 120 | else | ||
| 121 | @columns.collect { |column| | ||
| 122 | column_with_prefix(column) | ||
| 123 | } | ||
| 124 | end | ||
| 125 | end | ||
| 126 | |||
| 127 | def type_to_config | ||
| 128 | { | ||
| 129 | :multi => :sql_attr_multi, | ||
| 130 | :datetime => :sql_attr_timestamp, | ||
| 131 | :string => :sql_attr_str2ordinal, | ||
| 132 | :float => :sql_attr_float, | ||
| 133 | :boolean => :sql_attr_bool, | ||
| 134 | :integer => :sql_attr_uint | ||
| 135 | }[type] | ||
| 136 | end | ||
| 137 | |||
| 138 | def include_as_association? | ||
| 139 | ! (type == :multi && (source == :query || source == :ranged_query)) | ||
| 140 | end | ||
| 141 | |||
| 142 | # Returns the configuration value that should be used for | ||
| 143 | # the attribute. | ||
| 144 | # Special case is the multi-valued attribute that needs some | ||
| 145 | # extra configuration. | ||
| 146 | # | ||
| 147 | def config_value(offset = nil) | ||
| 148 | if type == :multi | ||
| 149 | multi_config = include_as_association? ? "field" : | ||
| 150 | source_value(offset).gsub(/\n\s*/, " ") | ||
| 151 | "uint #{unique_name} from #{multi_config}" | ||
| 152 | else | ||
| 153 | unique_name | ||
| 154 | end | ||
| 155 | end | ||
| 156 | |||
| 157 | # Returns the unique name of the attribute - which is either the alias of | ||
| 158 | # the attribute, or the name of the only column - if there is only one. If | ||
| 159 | # there isn't, there should be an alias. Else things probably won't work. | ||
| 160 | # Consider yourself warned. | ||
| 161 | # | ||
| 162 | def unique_name | ||
| 163 | if @columns.length == 1 | ||
| 164 | @alias || @columns.first.__name | ||
| 165 | else | ||
| 166 | @alias | ||
| 167 | end | ||
| 168 | end | ||
| 169 | |||
| 170 | # Returns the type of the column. If that's not already set, it returns | ||
| 171 | # :multi if there's the possibility of more than one value, :string if | ||
| 172 | # there's more than one association, otherwise it figures out what the | ||
| 173 | # actual column's datatype is and returns that. | ||
| 174 | # | ||
| 175 | def type | ||
| 176 | @type ||= begin | ||
| 177 | base_type = case | ||
| 178 | when is_many?, is_many_ints? | ||
| 179 | :multi | ||
| 180 | when @associations.values.flatten.length > 1 | ||
| 181 | :string | ||
| 182 | else | ||
| 183 | translated_type_from_database | ||
| 184 | end | ||
| 185 | |||
| 186 | if base_type == :string && @crc | ||
| 187 | :integer | ||
| 188 | else | ||
| 189 | @crc = false | ||
| 190 | base_type | ||
| 191 | end | ||
| 192 | end | ||
| 193 | end | ||
| 194 | |||
| 195 | def to_facet | ||
| 196 | return nil unless @faceted | ||
| 197 | |||
| 198 | ThinkingSphinx::Facet.new(self) | ||
| 199 | end | ||
| 200 | |||
| 201 | private | ||
| 202 | |||
| 203 | def source_value(offset) | ||
| 204 | if is_string? | ||
| 205 | "#{source.to_s.dasherize}; #{columns.first.__name}" | ||
| 206 | elsif source == :ranged_query | ||
| 207 | "ranged-query; #{query offset} #{query_clause}; #{range_query}" | ||
| 208 | else | ||
| 209 | "query; #{query offset}" | ||
| 210 | end | ||
| 211 | end | ||
| 212 | |||
| 213 | def query(offset) | ||
| 214 | assoc = association_for_mva | ||
| 215 | raise "Could not determine SQL for MVA" if assoc.nil? | ||
| 216 | |||
| 217 | <<-SQL | ||
| 218 | SELECT #{foreign_key_for_mva assoc} | ||
| 219 | #{ThinkingSphinx.unique_id_expression(offset)} AS #{quote_column('id')}, | ||
| 220 | #{primary_key_for_mva(assoc)} AS #{quote_column(unique_name)} | ||
| 221 | FROM #{quote_table_name assoc.table} | ||
| 222 | SQL | ||
| 223 | end | ||
| 224 | |||
| 225 | def query_clause | ||
| 226 | foreign_key = foreign_key_for_mva association_for_mva | ||
| 227 | "WHERE #{foreign_key} >= $start AND #{foreign_key} <= $end" | ||
| 228 | end | ||
| 229 | |||
| 230 | def range_query | ||
| 231 | assoc = association_for_mva | ||
| 232 | foreign_key = foreign_key_for_mva assoc | ||
| 233 | "SELECT MIN(#{foreign_key}), MAX(#{foreign_key}) FROM #{quote_table_name assoc.table}" | ||
| 234 | end | ||
| 235 | |||
| 236 | def primary_key_for_mva(assoc) | ||
| 237 | quote_with_table( | ||
| 238 | assoc.table, assoc.primary_key_from_reflection || columns.first.__name | ||
| 239 | ) | ||
| 240 | end | ||
| 241 | |||
| 242 | def foreign_key_for_mva(assoc) | ||
| 243 | quote_with_table assoc.table, assoc.reflection.primary_key_name | ||
| 244 | end | ||
| 245 | |||
| 246 | def association_for_mva | ||
| 247 | @association_for_mva ||= associations[columns.first].detect { |assoc| | ||
| 248 | assoc.has_column?(columns.first.__name) | ||
| 249 | } | ||
| 250 | end | ||
| 251 | |||
| 252 | def adapter | ||
| 253 | @adapter ||= @model.sphinx_database_adapter | ||
| 254 | end | ||
| 255 | |||
| 256 | def quote_with_table(table, column) | ||
| 257 | "#{quote_table_name(table)}.#{quote_column(column)}" | ||
| 258 | end | ||
| 259 | |||
| 260 | def quote_column(column) | ||
| 261 | @model.connection.quote_column_name(column) | ||
| 262 | end | ||
| 263 | |||
| 264 | def quote_table_name(table_name) | ||
| 265 | @model.connection.quote_table_name(table_name) | ||
| 266 | end | ||
| 267 | |||
| 268 | # Indication of whether the columns should be concatenated with a space | ||
| 269 | # between each value. True if there's either multiple sources or multiple | ||
| 270 | # associations. | ||
| 271 | # | ||
| 272 | def concat_ws? | ||
| 273 | multiple_associations? || @columns.length > 1 | ||
| 274 | end | ||
| 275 | |||
| 276 | # Checks whether any column requires multiple associations (which only | ||
| 277 | # happens for polymorphic situations). | ||
| 278 | # | ||
| 279 | def multiple_associations? | ||
| 280 | associations.any? { |col,assocs| assocs.length > 1 } | ||
| 281 | end | ||
| 282 | |||
| 283 | # Builds a column reference tied to the appropriate associations. This | ||
| 284 | # dives into the associations hash and their corresponding joins to | ||
| 285 | # figure out how to correctly reference a column in SQL. | ||
| 286 | # | ||
| 287 | def column_with_prefix(column) | ||
| 288 | if column.is_string? | ||
| 289 | column.__name | ||
| 290 | elsif associations[column].empty? | ||
| 291 | "#{@model.quoted_table_name}.#{quote_column(column.__name)}" | ||
| 292 | else | ||
| 293 | associations[column].collect { |assoc| | ||
| 294 | assoc.has_column?(column.__name) ? | ||
| 295 | "#{quote_table_name(assoc.join.aliased_table_name)}" + | ||
| 296 | ".#{quote_column(column.__name)}" : | ||
| 297 | nil | ||
| 298 | }.compact.join(', ') | ||
| 299 | end | ||
| 300 | end | ||
| 301 | |||
| 302 | # Could there be more than one value related to the parent record? If so, | ||
| 303 | # then this will return true. If not, false. It's that simple. | ||
| 304 | # | ||
| 305 | def is_many? | ||
| 306 | associations.values.flatten.any? { |assoc| assoc.is_many? } | ||
| 307 | end | ||
| 308 | |||
| 309 | def is_many_ints? | ||
| 310 | concat_ws? && all_ints? | ||
| 311 | end | ||
| 312 | |||
| 313 | # Returns true if any of the columns are string values, instead of database | ||
| 314 | # column references. | ||
| 315 | def is_string? | ||
| 316 | columns.all? { |col| col.is_string? } | ||
| 317 | end | ||
| 318 | |||
| 319 | def all_ints? | ||
| 320 | @columns.all? { |col| | ||
| 321 | klasses = @associations[col].empty? ? [@model] : | ||
| 322 | @associations[col].collect { |assoc| assoc.reflection.klass } | ||
| 323 | klasses.all? { |klass| | ||
| 324 | column = klass.columns.detect { |column| column.name == col.__name.to_s } | ||
| 325 | !column.nil? && column.type == :integer | ||
| 326 | } | ||
| 327 | } | ||
| 328 | end | ||
| 329 | |||
| 330 | def type_from_database | ||
| 331 | klass = @associations.values.flatten.first ? | ||
| 332 | @associations.values.flatten.first.reflection.klass : @model | ||
| 333 | |||
| 334 | klass.columns.detect { |col| | ||
| 335 | @columns.collect { |c| c.__name.to_s }.include? col.name | ||
| 336 | }.type | ||
| 337 | end | ||
| 338 | |||
| 339 | def translated_type_from_database | ||
| 340 | case type_from_db = type_from_database | ||
| 341 | when :datetime, :string, :float, :boolean, :integer | ||
| 342 | type_from_db | ||
| 343 | when :decimal | ||
| 344 | :float | ||
| 345 | when :timestamp, :date | ||
| 346 | :datetime | ||
| 347 | else | ||
| 348 | raise <<-MESSAGE | ||
| 349 | |||
| 350 | Cannot automatically map column type #{type_from_db} to an equivalent Sphinx | ||
| 351 | type (integer, float, boolean, datetime, string as ordinal). You could try to | ||
| 352 | explicitly convert the column's value in your define_index block: | ||
| 353 | has "CAST(column AS INT)", :type => :integer, :as => :column | ||
| 354 | MESSAGE | ||
| 355 | end | ||
| 356 | end | ||
| 357 | end | ||
| 358 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/class_facet.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/class_facet.rb deleted file mode 100644 index cb301b8..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/class_facet.rb +++ /dev/null | |||
| @@ -1,15 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | class ClassFacet < ThinkingSphinx::Facet | ||
| 3 | def name | ||
| 4 | :class | ||
| 5 | end | ||
| 6 | |||
| 7 | def attribute_name | ||
| 8 | "class_crc" | ||
| 9 | end | ||
| 10 | |||
| 11 | def value(object, attribute_value) | ||
| 12 | object.class.name | ||
| 13 | end | ||
| 14 | end | ||
| 15 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/collection.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/collection.rb deleted file mode 100644 index 406c2ae..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/collection.rb +++ /dev/null | |||
| @@ -1,147 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | class Collection < ::Array | ||
| 3 | attr_reader :total_entries, :total_pages, :current_page, :per_page | ||
| 4 | attr_accessor :results | ||
| 5 | |||
| 6 | # Compatibility with older versions of will_paginate | ||
| 7 | alias_method :page_count, :total_pages | ||
| 8 | |||
| 9 | def initialize(page, per_page, entries, total_entries) | ||
| 10 | @current_page, @per_page, @total_entries = page, per_page, total_entries | ||
| 11 | |||
| 12 | @total_pages = (entries / @per_page.to_f).ceil | ||
| 13 | end | ||
| 14 | |||
| 15 | def self.ids_from_results(results, page, limit, options) | ||
| 16 | collection = self.new(page, limit, | ||
| 17 | results[:total] || 0, results[:total_found] || 0 | ||
| 18 | ) | ||
| 19 | collection.results = results | ||
| 20 | collection.replace results[:matches].collect { |match| | ||
| 21 | match[:attributes]["sphinx_internal_id"] | ||
| 22 | } | ||
| 23 | return collection | ||
| 24 | end | ||
| 25 | |||
| 26 | def self.create_from_results(results, page, limit, options) | ||
| 27 | collection = self.new(page, limit, | ||
| 28 | results[:total] || 0, results[:total_found] || 0 | ||
| 29 | ) | ||
| 30 | collection.results = results | ||
| 31 | collection.replace instances_from_matches(results[:matches], options) | ||
| 32 | return collection | ||
| 33 | end | ||
| 34 | |||
| 35 | def self.instances_from_matches(matches, options = {}) | ||
| 36 | if klass = options[:class] | ||
| 37 | instances_from_class klass, matches, options | ||
| 38 | else | ||
| 39 | instances_from_classes matches, options | ||
| 40 | end | ||
| 41 | end | ||
| 42 | |||
| 43 | def self.instances_from_class(klass, matches, options = {}) | ||
| 44 | index_options = klass.sphinx_index_options | ||
| 45 | |||
| 46 | ids = matches.collect { |match| match[:attributes]["sphinx_internal_id"] } | ||
| 47 | instances = ids.length > 0 ? klass.find( | ||
| 48 | :all, | ||
| 49 | :conditions => {klass.primary_key.to_sym => ids}, | ||
| 50 | :include => (options[:include] || index_options[:include]), | ||
| 51 | :select => (options[:select] || index_options[:select]), | ||
| 52 | :order => (options[:sql_order] || index_options[:sql_order]) | ||
| 53 | ) : [] | ||
| 54 | |||
| 55 | # Raise an exception if we find records in Sphinx but not in the DB, so | ||
| 56 | # the search method can retry without them. See | ||
| 57 | # ThinkingSphinx::Search.retry_search_on_stale_index. | ||
| 58 | if options[:raise_on_stale] && instances.length < ids.length | ||
| 59 | stale_ids = ids - instances.map {|i| i.id } | ||
| 60 | raise StaleIdsException, stale_ids | ||
| 61 | end | ||
| 62 | |||
| 63 | # if the user has specified an SQL order, return the collection | ||
| 64 | # without rearranging it into the Sphinx order | ||
| 65 | return instances if options[:sql_order] | ||
| 66 | |||
| 67 | ids.collect { |obj_id| | ||
| 68 | instances.detect { |obj| obj.id == obj_id } | ||
| 69 | } | ||
| 70 | end | ||
| 71 | |||
| 72 | # Group results by class and call #find(:all) once for each group to reduce | ||
| 73 | # the number of #find's in multi-model searches. | ||
| 74 | # | ||
| 75 | def self.instances_from_classes(matches, options = {}) | ||
| 76 | groups = matches.group_by { |match| match[:attributes]["class_crc"] } | ||
| 77 | groups.each do |crc, group| | ||
| 78 | group.replace( | ||
| 79 | instances_from_class(class_from_crc(crc), group, options) | ||
| 80 | ) | ||
| 81 | end | ||
| 82 | |||
| 83 | matches.collect do |match| | ||
| 84 | groups.detect { |crc, group| | ||
| 85 | crc == match[:attributes]["class_crc"] | ||
| 86 | }[1].detect { |obj| | ||
| 87 | obj.id == match[:attributes]["sphinx_internal_id"] | ||
| 88 | } | ||
| 89 | end | ||
| 90 | end | ||
| 91 | |||
| 92 | def self.class_from_crc(crc) | ||
| 93 | @@models_by_crc ||= ThinkingSphinx.indexed_models.inject({}) do |hash, model| | ||
| 94 | hash[model.constantize.to_crc32] = model | ||
| 95 | model.constantize.subclasses.each { |subclass| | ||
| 96 | hash[subclass.to_crc32] = subclass.name | ||
| 97 | } | ||
| 98 | hash | ||
| 99 | end | ||
| 100 | @@models_by_crc[crc].constantize | ||
| 101 | end | ||
| 102 | |||
| 103 | def previous_page | ||
| 104 | current_page > 1 ? (current_page - 1) : nil | ||
| 105 | end | ||
| 106 | |||
| 107 | def next_page | ||
| 108 | current_page < total_pages ? (current_page + 1): nil | ||
| 109 | end | ||
| 110 | |||
| 111 | def offset | ||
| 112 | (current_page - 1) * @per_page | ||
| 113 | end | ||
| 114 | |||
| 115 | def method_missing(method, *args, &block) | ||
| 116 | super unless method.to_s[/^each_with_.*/] | ||
| 117 | |||
| 118 | each_with_attribute method.to_s.gsub(/^each_with_/, ''), &block | ||
| 119 | end | ||
| 120 | |||
| 121 | def each_with_groupby_and_count(&block) | ||
| 122 | results[:matches].each_with_index do |match, index| | ||
| 123 | yield self[index], match[:attributes]["@groupby"], match[:attributes]["@count"] | ||
| 124 | end | ||
| 125 | end | ||
| 126 | |||
| 127 | def each_with_attribute(attribute, &block) | ||
| 128 | results[:matches].each_with_index do |match, index| | ||
| 129 | yield self[index], (match[:attributes][attribute] || match[:attributes]["@#{attribute}"]) | ||
| 130 | end | ||
| 131 | end | ||
| 132 | |||
| 133 | def each_with_weighting(&block) | ||
| 134 | results[:matches].each_with_index do |match, index| | ||
| 135 | yield self[index], match[:weight] | ||
| 136 | end | ||
| 137 | end | ||
| 138 | |||
| 139 | def inject_with_groupby_and_count(initial = nil, &block) | ||
| 140 | index = -1 | ||
| 141 | results[:matches].inject(initial) do |memo, match| | ||
| 142 | index += 1 | ||
| 143 | yield memo, self[index], match[:attributes]["@groupby"], match[:attributes]["@count"] | ||
| 144 | end | ||
| 145 | end | ||
| 146 | end | ||
| 147 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/configuration.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/configuration.rb deleted file mode 100644 index 31543f5..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/configuration.rb +++ /dev/null | |||
| @@ -1,237 +0,0 @@ | |||
| 1 | require 'erb' | ||
| 2 | require 'singleton' | ||
| 3 | |||
| 4 | module ThinkingSphinx | ||
| 5 | # This class both keeps track of the configuration settings for Sphinx and | ||
| 6 | # also generates the resulting file for Sphinx to use. | ||
| 7 | # | ||
| 8 | # Here are the default settings, relative to RAILS_ROOT where relevant: | ||
| 9 | # | ||
| 10 | # config file:: config/#{environment}.sphinx.conf | ||
| 11 | # searchd log file:: log/searchd.log | ||
| 12 | # query log file:: log/searchd.query.log | ||
| 13 | # pid file:: log/searchd.#{environment}.pid | ||
| 14 | # searchd files:: db/sphinx/#{environment}/ | ||
| 15 | # address:: 127.0.0.1 | ||
| 16 | # port:: 3312 | ||
| 17 | # allow star:: false | ||
| 18 | # min prefix length:: 1 | ||
| 19 | # min infix length:: 1 | ||
| 20 | # mem limit:: 64M | ||
| 21 | # max matches:: 1000 | ||
| 22 | # morphology:: stem_en | ||
| 23 | # charset type:: utf-8 | ||
| 24 | # charset table:: nil | ||
| 25 | # ignore chars:: nil | ||
| 26 | # html strip:: false | ||
| 27 | # html remove elements:: '' | ||
| 28 | # | ||
| 29 | # If you want to change these settings, create a YAML file at | ||
| 30 | # config/sphinx.yml with settings for each environment, in a similar | ||
| 31 | # fashion to database.yml - using the following keys: config_file, | ||
| 32 | # searchd_log_file, query_log_file, pid_file, searchd_file_path, port, | ||
| 33 | # allow_star, enable_star, min_prefix_len, min_infix_len, mem_limit, | ||
| 34 | # max_matches, # morphology, charset_type, charset_table, ignore_chars, | ||
| 35 | # html_strip, # html_remove_elements. I think you've got the idea. | ||
| 36 | # | ||
| 37 | # Each setting in the YAML file is optional - so only put in the ones you | ||
| 38 | # want to change. | ||
| 39 | # | ||
| 40 | # Keep in mind, if for some particular reason you're using a version of | ||
| 41 | # Sphinx older than 0.9.8 r871 (that's prior to the proper 0.9.8 release), | ||
| 42 | # don't set allow_star to true. | ||
| 43 | # | ||
| 44 | class Configuration | ||
| 45 | include Singleton | ||
| 46 | |||
| 47 | SourceOptions = %w( mysql_connect_flags sql_range_step sql_query_pre | ||
| 48 | sql_query_post sql_ranged_throttle sql_query_post_index ) | ||
| 49 | |||
| 50 | IndexOptions = %w( charset_table charset_type docinfo enable_star | ||
| 51 | exceptions html_index_attrs html_remove_elements html_strip ignore_chars | ||
| 52 | min_infix_len min_prefix_len min_word_len mlock morphology ngram_chars | ||
| 53 | ngram_len phrase_boundary phrase_boundary_step preopen stopwords | ||
| 54 | wordforms ) | ||
| 55 | |||
| 56 | attr_accessor :config_file, :searchd_log_file, :query_log_file, | ||
| 57 | :pid_file, :searchd_file_path, :address, :port, :allow_star, | ||
| 58 | :database_yml_file, :app_root, :bin_path, :model_directories | ||
| 59 | |||
| 60 | attr_accessor :source_options, :index_options | ||
| 61 | |||
| 62 | attr_reader :environment, :configuration | ||
| 63 | |||
| 64 | # Load in the configuration settings - this will look for config/sphinx.yml | ||
| 65 | # and parse it according to the current environment. | ||
| 66 | # | ||
| 67 | def initialize(app_root = Dir.pwd) | ||
| 68 | self.reset | ||
| 69 | end | ||
| 70 | |||
| 71 | def reset | ||
| 72 | self.app_root = RAILS_ROOT if defined?(RAILS_ROOT) | ||
| 73 | self.app_root = Merb.root if defined?(Merb) | ||
| 74 | self.app_root ||= app_root | ||
| 75 | |||
| 76 | @configuration = Riddle::Configuration.new | ||
| 77 | @configuration.searchd.address = "127.0.0.1" | ||
| 78 | @configuration.searchd.port = 3312 | ||
| 79 | @configuration.searchd.pid_file = "#{self.app_root}/log/searchd.#{environment}.pid" | ||
| 80 | @configuration.searchd.log = "#{self.app_root}/log/searchd.log" | ||
| 81 | @configuration.searchd.query_log = "#{self.app_root}/log/searchd.query.log" | ||
| 82 | |||
| 83 | self.database_yml_file = "#{self.app_root}/config/database.yml" | ||
| 84 | self.config_file = "#{self.app_root}/config/#{environment}.sphinx.conf" | ||
| 85 | self.searchd_file_path = "#{self.app_root}/db/sphinx/#{environment}" | ||
| 86 | self.allow_star = false | ||
| 87 | self.bin_path = "" | ||
| 88 | self.model_directories = ["#{app_root}/app/models/"] + | ||
| 89 | Dir.glob("#{app_root}/vendor/plugins/*/app/models/") | ||
| 90 | |||
| 91 | self.source_options = {} | ||
| 92 | self.index_options = { | ||
| 93 | :charset_type => "utf-8", | ||
| 94 | :morphology => "stem_en" | ||
| 95 | } | ||
| 96 | |||
| 97 | parse_config | ||
| 98 | |||
| 99 | self | ||
| 100 | end | ||
| 101 | |||
| 102 | def self.environment | ||
| 103 | @@environment ||= ( | ||
| 104 | defined?(Merb) ? Merb.environment : ENV['RAILS_ENV'] | ||
| 105 | ) || "development" | ||
| 106 | end | ||
| 107 | |||
| 108 | def environment | ||
| 109 | self.class.environment | ||
| 110 | end | ||
| 111 | |||
| 112 | def controller | ||
| 113 | @controller ||= Riddle::Controller.new(@configuration, self.config_file) | ||
| 114 | end | ||
| 115 | |||
| 116 | # Generate the config file for Sphinx by using all the settings defined and | ||
| 117 | # looping through all the models with indexes to build the relevant | ||
| 118 | # indexer and searchd configuration, and sources and indexes details. | ||
| 119 | # | ||
| 120 | def build(file_path=nil) | ||
| 121 | load_models | ||
| 122 | file_path ||= "#{self.config_file}" | ||
| 123 | |||
| 124 | @configuration.indexes.clear | ||
| 125 | |||
| 126 | ThinkingSphinx.indexed_models.each_with_index do |model, model_index| | ||
| 127 | @configuration.indexes.concat model.constantize.to_riddle(model_index) | ||
| 128 | end | ||
| 129 | |||
| 130 | open(file_path, "w") do |file| | ||
| 131 | file.write @configuration.render | ||
| 132 | end | ||
| 133 | end | ||
| 134 | |||
| 135 | # Make sure all models are loaded - without reloading any that | ||
| 136 | # ActiveRecord::Base is already aware of (otherwise we start to hit some | ||
| 137 | # messy dependencies issues). | ||
| 138 | # | ||
| 139 | def load_models | ||
| 140 | self.model_directories.each do |base| | ||
| 141 | Dir["#{base}**/*.rb"].each do |file| | ||
| 142 | model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1') | ||
| 143 | |||
| 144 | next if model_name.nil? | ||
| 145 | next if ::ActiveRecord::Base.send(:subclasses).detect { |model| | ||
| 146 | model.name == model_name | ||
| 147 | } | ||
| 148 | |||
| 149 | begin | ||
| 150 | model_name.camelize.constantize | ||
| 151 | rescue LoadError | ||
| 152 | model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry | ||
| 153 | rescue NameError | ||
| 154 | next | ||
| 155 | end | ||
| 156 | end | ||
| 157 | end | ||
| 158 | end | ||
| 159 | |||
| 160 | def address | ||
| 161 | @configuration.searchd.address | ||
| 162 | end | ||
| 163 | |||
| 164 | def address=(address) | ||
| 165 | @configuration.searchd.address = address | ||
| 166 | end | ||
| 167 | |||
| 168 | def port | ||
| 169 | @configuration.searchd.port | ||
| 170 | end | ||
| 171 | |||
| 172 | def port=(port) | ||
| 173 | @configuration.searchd.port = port | ||
| 174 | end | ||
| 175 | |||
| 176 | def pid_file | ||
| 177 | @configuration.searchd.pid_file | ||
| 178 | end | ||
| 179 | |||
| 180 | def pid_file=(pid_file) | ||
| 181 | @configuration.searchd.pid_file = pid_file | ||
| 182 | end | ||
| 183 | |||
| 184 | def searchd_log_file | ||
| 185 | @configuration.searchd.log | ||
| 186 | end | ||
| 187 | |||
| 188 | def searchd_log_file=(file) | ||
| 189 | @configuration.searchd.log = file | ||
| 190 | end | ||
| 191 | |||
| 192 | def query_log_file | ||
| 193 | @configuration.searchd.query_log | ||
| 194 | end | ||
| 195 | |||
| 196 | def query_log_file=(file) | ||
| 197 | @configuration.searchd.query_log = file | ||
| 198 | end | ||
| 199 | |||
| 200 | private | ||
| 201 | |||
| 202 | # Parse the config/sphinx.yml file - if it exists - then use the attribute | ||
| 203 | # accessors to set the appropriate values. Nothing too clever. | ||
| 204 | # | ||
| 205 | def parse_config | ||
| 206 | path = "#{app_root}/config/sphinx.yml" | ||
| 207 | return unless File.exists?(path) | ||
| 208 | |||
| 209 | conf = YAML::load(ERB.new(IO.read(path)).result)[environment] | ||
| 210 | |||
| 211 | conf.each do |key,value| | ||
| 212 | self.send("#{key}=", value) if self.methods.include?("#{key}=") | ||
| 213 | |||
| 214 | set_sphinx_setting self.source_options, key, value, SourceOptions | ||
| 215 | set_sphinx_setting self.index_options, key, value, IndexOptions | ||
| 216 | set_sphinx_setting @configuration.searchd, key, value | ||
| 217 | set_sphinx_setting @configuration.indexer, key, value | ||
| 218 | end unless conf.nil? | ||
| 219 | |||
| 220 | self.bin_path += '/' unless self.bin_path.blank? | ||
| 221 | |||
| 222 | if self.allow_star | ||
| 223 | self.index_options[:enable_star] = true | ||
| 224 | self.index_options[:min_prefix_len] = 1 | ||
| 225 | end | ||
| 226 | end | ||
| 227 | |||
| 228 | def set_sphinx_setting(object, key, value, allowed = {}) | ||
| 229 | if object.is_a?(Hash) | ||
| 230 | object[key.to_sym] = value if allowed.include?(key.to_s) | ||
| 231 | else | ||
| 232 | object.send("#{key}=", value) if object.methods.include?("#{key}") | ||
| 233 | send("#{key}=", value) if self.methods.include?("#{key}") | ||
| 234 | end | ||
| 235 | end | ||
| 236 | end | ||
| 237 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/core/string.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/core/string.rb deleted file mode 100644 index 7ab3e62..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/core/string.rb +++ /dev/null | |||
| @@ -1,15 +0,0 @@ | |||
| 1 | require 'zlib' | ||
| 2 | |||
| 3 | module ThinkingSphinx | ||
| 4 | module Core | ||
| 5 | module String | ||
| 6 | def to_crc32 | ||
| 7 | Zlib.crc32 self | ||
| 8 | end | ||
| 9 | end | ||
| 10 | end | ||
| 11 | end | ||
| 12 | |||
| 13 | class String | ||
| 14 | include ThinkingSphinx::Core::String | ||
| 15 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas.rb deleted file mode 100644 index 57c396e..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas.rb +++ /dev/null | |||
| @@ -1,27 +0,0 @@ | |||
| 1 | require 'thinking_sphinx/deltas/default_delta' | ||
| 2 | require 'thinking_sphinx/deltas/delayed_delta' | ||
| 3 | require 'thinking_sphinx/deltas/datetime_delta' | ||
| 4 | |||
| 5 | module ThinkingSphinx | ||
| 6 | module Deltas | ||
| 7 | def self.parse(index, options) | ||
| 8 | delta_option = options.delete(:delta) | ||
| 9 | case delta_option | ||
| 10 | when TrueClass, :default | ||
| 11 | DefaultDelta.new index, options | ||
| 12 | when :delayed | ||
| 13 | DelayedDelta.new index, options | ||
| 14 | when :datetime | ||
| 15 | DatetimeDelta.new index, options | ||
| 16 | when FalseClass, nil | ||
| 17 | nil | ||
| 18 | else | ||
| 19 | if delta_option.ancestors.include?(ThinkingSphinx::Deltas::DefaultDelta) | ||
| 20 | delta_option.new index, options | ||
| 21 | else | ||
| 22 | raise "Unknown delta type" | ||
| 23 | end | ||
| 24 | end | ||
| 25 | end | ||
| 26 | end | ||
| 27 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/datetime_delta.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/datetime_delta.rb deleted file mode 100644 index 2ee46d4..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/datetime_delta.rb +++ /dev/null | |||
| @@ -1,50 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | module Deltas | ||
| 3 | class DatetimeDelta < ThinkingSphinx::Deltas::DefaultDelta | ||
| 4 | attr_accessor :column, :threshold | ||
| 5 | |||
| 6 | def initialize(index, options) | ||
| 7 | @index = index | ||
| 8 | @column = options.delete(:delta_column) || :updated_at | ||
| 9 | @threshold = options.delete(:threshold) || 1.day | ||
| 10 | end | ||
| 11 | |||
| 12 | def index(model, instance = nil) | ||
| 13 | # do nothing | ||
| 14 | true | ||
| 15 | end | ||
| 16 | |||
| 17 | def delayed_index(model) | ||
| 18 | config = ThinkingSphinx::Configuration.instance | ||
| 19 | rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : "" | ||
| 20 | |||
| 21 | output = `#{config.bin_path}indexer --config #{config.config_file} #{rotate} #{delta_index_name model}` | ||
| 22 | output += `#{config.bin_path}indexer --config #{config.config_file} #{rotate} --merge #{core_index_name model} #{delta_index_name model} --merge-dst-range sphinx_deleted 0 0` | ||
| 23 | puts output unless ThinkingSphinx.suppress_delta_output? | ||
| 24 | |||
| 25 | true | ||
| 26 | end | ||
| 27 | |||
| 28 | def toggle(instance) | ||
| 29 | # do nothing | ||
| 30 | end | ||
| 31 | |||
| 32 | def toggled(instance) | ||
| 33 | instance.send(@column) > @threshold.ago | ||
| 34 | end | ||
| 35 | |||
| 36 | def reset_query(model) | ||
| 37 | nil | ||
| 38 | end | ||
| 39 | |||
| 40 | def clause(model, toggled) | ||
| 41 | if toggled | ||
| 42 | "#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" + | ||
| 43 | " > #{adapter.time_difference(@threshold)}" | ||
| 44 | else | ||
| 45 | nil | ||
| 46 | end | ||
| 47 | end | ||
| 48 | end | ||
| 49 | end | ||
| 50 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/default_delta.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/default_delta.rb deleted file mode 100644 index c973612..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/default_delta.rb +++ /dev/null | |||
| @@ -1,67 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | module Deltas | ||
| 3 | class DefaultDelta | ||
| 4 | attr_accessor :column | ||
| 5 | |||
| 6 | def initialize(index, options) | ||
| 7 | @index = index | ||
| 8 | @column = options.delete(:delta_column) || :delta | ||
| 9 | end | ||
| 10 | |||
| 11 | def index(model, instance = nil) | ||
| 12 | return true unless ThinkingSphinx.updates_enabled? && | ||
| 13 | ThinkingSphinx.deltas_enabled? | ||
| 14 | return true if instance && !toggled(instance) | ||
| 15 | |||
| 16 | config = ThinkingSphinx::Configuration.instance | ||
| 17 | client = Riddle::Client.new config.address, config.port | ||
| 18 | rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : "" | ||
| 19 | |||
| 20 | output = `#{config.bin_path}indexer --config #{config.config_file} #{rotate} #{delta_index_name model}` | ||
| 21 | puts(output) unless ThinkingSphinx.suppress_delta_output? | ||
| 22 | |||
| 23 | client.update( | ||
| 24 | core_index_name(model), | ||
| 25 | ['sphinx_deleted'], | ||
| 26 | {instance.sphinx_document_id => [1]} | ||
| 27 | ) if instance && ThinkingSphinx.sphinx_running? && instance.in_both_indexes? | ||
| 28 | |||
| 29 | true | ||
| 30 | end | ||
| 31 | |||
| 32 | def toggle(instance) | ||
| 33 | instance.delta = true | ||
| 34 | end | ||
| 35 | |||
| 36 | def toggled(instance) | ||
| 37 | instance.delta | ||
| 38 | end | ||
| 39 | |||
| 40 | def reset_query(model) | ||
| 41 | "UPDATE #{model.quoted_table_name} SET " + | ||
| 42 | "#{@index.quote_column(@column.to_s)} = #{adapter.boolean(false)}" | ||
| 43 | end | ||
| 44 | |||
| 45 | def clause(model, toggled) | ||
| 46 | "#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" + | ||
| 47 | " = #{adapter.boolean(toggled)}" | ||
| 48 | end | ||
| 49 | |||
| 50 | protected | ||
| 51 | |||
| 52 | def core_index_name(model) | ||
| 53 | "#{model.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_core" | ||
| 54 | end | ||
| 55 | |||
| 56 | def delta_index_name(model) | ||
| 57 | "#{model.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_delta" | ||
| 58 | end | ||
| 59 | |||
| 60 | private | ||
| 61 | |||
| 62 | def adapter | ||
| 63 | @adapter = @index.model.sphinx_database_adapter | ||
| 64 | end | ||
| 65 | end | ||
| 66 | end | ||
| 67 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta.rb deleted file mode 100644 index e95298b..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta.rb +++ /dev/null | |||
| @@ -1,25 +0,0 @@ | |||
| 1 | require 'delayed/job' | ||
| 2 | |||
| 3 | require 'thinking_sphinx/deltas/delayed_delta/delta_job' | ||
| 4 | require 'thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job' | ||
| 5 | require 'thinking_sphinx/deltas/delayed_delta/job' | ||
| 6 | |||
| 7 | module ThinkingSphinx | ||
| 8 | module Deltas | ||
| 9 | class DelayedDelta < ThinkingSphinx::Deltas::DefaultDelta | ||
| 10 | def index(model, instance = nil) | ||
| 11 | ThinkingSphinx::Deltas::Job.enqueue( | ||
| 12 | ThinkingSphinx::Deltas::DeltaJob.new(delta_index_name(model)) | ||
| 13 | ) | ||
| 14 | |||
| 15 | Delayed::Job.enqueue( | ||
| 16 | ThinkingSphinx::Deltas::FlagAsDeletedJob.new( | ||
| 17 | core_index_name(model), instance.sphinx_document_id | ||
| 18 | ) | ||
| 19 | ) if instance | ||
| 20 | |||
| 21 | true | ||
| 22 | end | ||
| 23 | end | ||
| 24 | end | ||
| 25 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb deleted file mode 100644 index f9511ec..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +++ /dev/null | |||
| @@ -1,24 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | module Deltas | ||
| 3 | class DeltaJob | ||
| 4 | attr_accessor :index | ||
| 5 | |||
| 6 | def initialize(index) | ||
| 7 | @index = index | ||
| 8 | end | ||
| 9 | |||
| 10 | def perform | ||
| 11 | return true unless ThinkingSphinx.updates_enabled? && | ||
| 12 | ThinkingSphinx.deltas_enabled? | ||
| 13 | |||
| 14 | config = ThinkingSphinx::Configuration.instance | ||
| 15 | client = Riddle::Client.new config.address, config.port | ||
| 16 | |||
| 17 | output = `#{config.bin_path}indexer --config #{config.config_file} --rotate #{index}` | ||
| 18 | puts output unless ThinkingSphinx.suppress_delta_output? | ||
| 19 | |||
| 20 | true | ||
| 21 | end | ||
| 22 | end | ||
| 23 | end | ||
| 24 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb deleted file mode 100644 index d6afd27..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +++ /dev/null | |||
| @@ -1,27 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | module Deltas | ||
| 3 | class FlagAsDeletedJob | ||
| 4 | attr_accessor :index, :document_id | ||
| 5 | |||
| 6 | def initialize(index, document_id) | ||
| 7 | @index, @document_id = index, document_id | ||
| 8 | end | ||
| 9 | |||
| 10 | def perform | ||
| 11 | return true unless ThinkingSphinx.updates_enabled? | ||
| 12 | |||
| 13 | config = ThinkingSphinx::Configuration.instance | ||
| 14 | client = Riddle::Client.new config.address, config.port | ||
| 15 | |||
| 16 | client.update( | ||
| 17 | @index, | ||
| 18 | ['sphinx_deleted'], | ||
| 19 | {@document_id => [1]} | ||
| 20 | ) if ThinkingSphinx.sphinx_running? && | ||
| 21 | ThinkingSphinx::Search.search_for_id(@document_id, @index) | ||
| 22 | |||
| 23 | true | ||
| 24 | end | ||
| 25 | end | ||
| 26 | end | ||
| 27 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta/job.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta/job.rb deleted file mode 100644 index de0a7cb..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/deltas/delayed_delta/job.rb +++ /dev/null | |||
| @@ -1,26 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | module Deltas | ||
| 3 | class Job < Delayed::Job | ||
| 4 | def self.enqueue(object, priority = 0) | ||
| 5 | super unless duplicates_exist(object) | ||
| 6 | end | ||
| 7 | |||
| 8 | def self.cancel_thinking_sphinx_jobs | ||
| 9 | if connection.tables.include?("delayed_jobs") | ||
| 10 | delete_all("handler LIKE '--- !ruby/object:ThinkingSphinx::Deltas::%'") | ||
| 11 | end | ||
| 12 | end | ||
| 13 | |||
| 14 | private | ||
| 15 | |||
| 16 | def self.duplicates_exist(object) | ||
| 17 | count( | ||
| 18 | :conditions => { | ||
| 19 | :handler => object.to_yaml, | ||
| 20 | :locked_at => nil | ||
| 21 | } | ||
| 22 | ) > 0 | ||
| 23 | end | ||
| 24 | end | ||
| 25 | end | ||
| 26 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/facet.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/facet.rb deleted file mode 100644 index e2db449..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/facet.rb +++ /dev/null | |||
| @@ -1,58 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | class Facet | ||
| 3 | attr_reader :reference | ||
| 4 | |||
| 5 | def initialize(reference) | ||
| 6 | @reference = reference | ||
| 7 | |||
| 8 | if reference.columns.length != 1 | ||
| 9 | raise "Can't translate Facets on multiple-column field or attribute" | ||
| 10 | end | ||
| 11 | end | ||
| 12 | |||
| 13 | def name | ||
| 14 | reference.unique_name | ||
| 15 | end | ||
| 16 | |||
| 17 | def attribute_name | ||
| 18 | # @attribute_name ||= case @reference | ||
| 19 | # when Attribute | ||
| 20 | # @reference.unique_name.to_s | ||
| 21 | # when Field | ||
| 22 | @attribute_name ||= @reference.unique_name.to_s + "_facet" | ||
| 23 | # end | ||
| 24 | end | ||
| 25 | |||
| 26 | def value(object, attribute_value) | ||
| 27 | return translate(object, attribute_value) if @reference.is_a?(Field) | ||
| 28 | |||
| 29 | case @reference.type | ||
| 30 | when :string | ||
| 31 | translate(object, attribute_value) | ||
| 32 | when :datetime | ||
| 33 | Time.at(attribute_value) | ||
| 34 | when :boolean | ||
| 35 | attribute_value > 0 | ||
| 36 | else | ||
| 37 | attribute_value | ||
| 38 | end | ||
| 39 | end | ||
| 40 | |||
| 41 | def to_s | ||
| 42 | name | ||
| 43 | end | ||
| 44 | |||
| 45 | private | ||
| 46 | |||
| 47 | def translate(object, attribute_value) | ||
| 48 | column.__stack.each { |method| | ||
| 49 | object = object.send(method) | ||
| 50 | } | ||
| 51 | object.send(column.__name) | ||
| 52 | end | ||
| 53 | |||
| 54 | def column | ||
| 55 | @reference.columns.first | ||
| 56 | end | ||
| 57 | end | ||
| 58 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/facet_collection.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/facet_collection.rb deleted file mode 100644 index 1ad9d1a..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/facet_collection.rb +++ /dev/null | |||
| @@ -1,60 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | class FacetCollection < Hash | ||
| 3 | attr_accessor :arguments | ||
| 4 | |||
| 5 | def initialize(arguments) | ||
| 6 | @arguments = arguments.clone | ||
| 7 | @attribute_values = {} | ||
| 8 | @facets = [] | ||
| 9 | end | ||
| 10 | |||
| 11 | def add_from_results(facet, results) | ||
| 12 | facet = facet_from_object(results.first, facet) if facet.is_a?(String) | ||
| 13 | |||
| 14 | self[facet.name] ||= {} | ||
| 15 | @attribute_values[facet.name] ||= {} | ||
| 16 | @facets << facet | ||
| 17 | |||
| 18 | results.each_with_groupby_and_count { |result, group, count| | ||
| 19 | facet_value = facet.value(result, group) | ||
| 20 | |||
| 21 | self[facet.name][facet_value] ||= 0 | ||
| 22 | self[facet.name][facet_value] += count | ||
| 23 | @attribute_values[facet.name][facet_value] = group | ||
| 24 | } | ||
| 25 | end | ||
| 26 | |||
| 27 | def for(hash = {}) | ||
| 28 | arguments = @arguments.clone | ||
| 29 | options = arguments.extract_options! | ||
| 30 | options[:with] ||= {} | ||
| 31 | |||
| 32 | hash.each do |key, value| | ||
| 33 | attrib = facet_for_key(key).attribute_name | ||
| 34 | options[:with][attrib] = underlying_value key, value | ||
| 35 | end | ||
| 36 | |||
| 37 | arguments << options | ||
| 38 | ThinkingSphinx::Search.search *arguments | ||
| 39 | end | ||
| 40 | |||
| 41 | private | ||
| 42 | |||
| 43 | def underlying_value(key, value) | ||
| 44 | case value | ||
| 45 | when Array | ||
| 46 | value.collect { |item| underlying_value(key, item) } | ||
| 47 | else | ||
| 48 | @attribute_values[key][value] | ||
| 49 | end | ||
| 50 | end | ||
| 51 | |||
| 52 | def facet_for_key(key) | ||
| 53 | @facets.detect { |facet| facet.name == key } | ||
| 54 | end | ||
| 55 | |||
| 56 | def facet_from_object(object, name) | ||
| 57 | object.sphinx_facets.detect { |facet| facet.attribute_name == name } | ||
| 58 | end | ||
| 59 | end | ||
| 60 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/field.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/field.rb deleted file mode 100644 index 9edaede..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/field.rb +++ /dev/null | |||
| @@ -1,172 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | # Fields - holding the string data which Sphinx indexes for your searches. | ||
| 3 | # This class isn't really useful to you unless you're hacking around with the | ||
| 4 | # internals of Thinking Sphinx - but hey, don't let that stop you. | ||
| 5 | # | ||
| 6 | # One key thing to remember - if you're using the field manually to | ||
| 7 | # generate SQL statements, you'll need to set the base model, and all the | ||
| 8 | # associations. Which can get messy. Use Index.link!, it really helps. | ||
| 9 | # | ||
| 10 | class Field | ||
| 11 | attr_accessor :alias, :columns, :sortable, :associations, :model, :infixes, | ||
| 12 | :prefixes, :faceted | ||
| 13 | |||
| 14 | # To create a new field, you'll need to pass in either a single Column | ||
| 15 | # or an array of them, and some (optional) options. The columns are | ||
| 16 | # references to the data that will make up the field. | ||
| 17 | # | ||
| 18 | # Valid options are: | ||
| 19 | # - :as => :alias_name | ||
| 20 | # - :sortable => true | ||
| 21 | # - :infixes => true | ||
| 22 | # - :prefixes => true | ||
| 23 | # | ||
| 24 | # Alias is only required in three circumstances: when there's | ||
| 25 | # another attribute or field with the same name, when the column name is | ||
| 26 | # 'id', or when there's more than one column. | ||
| 27 | # | ||
| 28 | # Sortable defaults to false - but is quite useful when set to true, as | ||
| 29 | # it creates an attribute with the same string value (which Sphinx converts | ||
| 30 | # to an integer value), which can be sorted by. Thinking Sphinx is smart | ||
| 31 | # enough to realise that when you specify fields in sort statements, you | ||
| 32 | # mean their respective attributes. | ||
| 33 | # | ||
| 34 | # If you have partial matching enabled (ie: enable_star), then you can | ||
| 35 | # specify certain fields to have their prefixes and infixes indexed. Keep | ||
| 36 | # in mind, though, that Sphinx's default is _all_ fields - so once you | ||
| 37 | # highlight a particular field, no other fields in the index will have | ||
| 38 | # these partial indexes. | ||
| 39 | # | ||
| 40 | # Here's some examples: | ||
| 41 | # | ||
| 42 | # Field.new( | ||
| 43 | # Column.new(:name) | ||
| 44 | # ) | ||
| 45 | # | ||
| 46 | # Field.new( | ||
| 47 | # [Column.new(:first_name), Column.new(:last_name)], | ||
| 48 | # :as => :name, :sortable => true | ||
| 49 | # ) | ||
| 50 | # | ||
| 51 | # Field.new( | ||
| 52 | # [Column.new(:posts, :subject), Column.new(:posts, :content)], | ||
| 53 | # :as => :posts, :prefixes => true | ||
| 54 | # ) | ||
| 55 | # | ||
| 56 | def initialize(columns, options = {}) | ||
| 57 | @columns = Array(columns) | ||
| 58 | @associations = {} | ||
| 59 | |||
| 60 | raise "Cannot define a field 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) } | ||
| 61 | |||
| 62 | @alias = options[:as] | ||
| 63 | @sortable = options[:sortable] || false | ||
| 64 | @infixes = options[:infixes] || false | ||
| 65 | @prefixes = options[:prefixes] || false | ||
| 66 | @faceted = options[:facet] || false | ||
| 67 | end | ||
| 68 | |||
| 69 | # Get the part of the SELECT clause related to this field. Don't forget | ||
| 70 | # to set your model and associations first though. | ||
| 71 | # | ||
| 72 | # This will concatenate strings if there's more than one data source or | ||
| 73 | # multiple data values (has_many or has_and_belongs_to_many associations). | ||
| 74 | # | ||
| 75 | def to_select_sql | ||
| 76 | clause = @columns.collect { |column| | ||
| 77 | column_with_prefix(column) | ||
| 78 | }.join(', ') | ||
| 79 | |||
| 80 | clause = adapter.concatenate(clause) if concat_ws? | ||
| 81 | clause = adapter.group_concatenate(clause) if is_many? | ||
| 82 | |||
| 83 | "#{adapter.cast_to_string clause } AS #{quote_column(unique_name)}" | ||
| 84 | end | ||
| 85 | |||
| 86 | # Get the part of the GROUP BY clause related to this field - if one is | ||
| 87 | # needed. If not, all you'll get back is nil. The latter will happen if | ||
| 88 | # there's multiple data values (read: a has_many or has_and_belongs_to_many | ||
| 89 | # association). | ||
| 90 | # | ||
| 91 | def to_group_sql | ||
| 92 | case | ||
| 93 | when is_many?, ThinkingSphinx.use_group_by_shortcut? | ||
| 94 | nil | ||
| 95 | else | ||
| 96 | @columns.collect { |column| | ||
| 97 | column_with_prefix(column) | ||
| 98 | } | ||
| 99 | end | ||
| 100 | end | ||
| 101 | |||
| 102 | # Returns the unique name of the field - which is either the alias of | ||
| 103 | # the field, or the name of the only column - if there is only one. If | ||
| 104 | # there isn't, there should be an alias. Else things probably won't work. | ||
| 105 | # Consider yourself warned. | ||
| 106 | # | ||
| 107 | def unique_name | ||
| 108 | if @columns.length == 1 | ||
| 109 | @alias || @columns.first.__name | ||
| 110 | else | ||
| 111 | @alias | ||
| 112 | end | ||
| 113 | end | ||
| 114 | |||
| 115 | def to_facet | ||
| 116 | return nil unless @faceted | ||
| 117 | |||
| 118 | ThinkingSphinx::Facet.new(self) | ||
| 119 | end | ||
| 120 | |||
| 121 | private | ||
| 122 | |||
| 123 | def adapter | ||
| 124 | @adapter ||= @model.sphinx_database_adapter | ||
| 125 | end | ||
| 126 | |||
| 127 | def quote_column(column) | ||
| 128 | @model.connection.quote_column_name(column) | ||
| 129 | end | ||
| 130 | |||
| 131 | # Indication of whether the columns should be concatenated with a space | ||
| 132 | # between each value. True if there's either multiple sources or multiple | ||
| 133 | # associations. | ||
| 134 | # | ||
| 135 | def concat_ws? | ||
| 136 | @columns.length > 1 || multiple_associations? | ||
| 137 | end | ||
| 138 | |||
| 139 | # Checks whether any column requires multiple associations (which only | ||
| 140 | # happens for polymorphic situations). | ||
| 141 | # | ||
| 142 | def multiple_associations? | ||
| 143 | associations.any? { |col,assocs| assocs.length > 1 } | ||
| 144 | end | ||
| 145 | |||
| 146 | # Builds a column reference tied to the appropriate associations. This | ||
| 147 | # dives into the associations hash and their corresponding joins to | ||
| 148 | # figure out how to correctly reference a column in SQL. | ||
| 149 | # | ||
| 150 | def column_with_prefix(column) | ||
| 151 | if column.is_string? | ||
| 152 | column.__name | ||
| 153 | elsif associations[column].empty? | ||
| 154 | "#{@model.quoted_table_name}.#{quote_column(column.__name)}" | ||
| 155 | else | ||
| 156 | associations[column].collect { |assoc| | ||
| 157 | assoc.has_column?(column.__name) ? | ||
| 158 | "#{@model.connection.quote_table_name(assoc.join.aliased_table_name)}" + | ||
| 159 | ".#{quote_column(column.__name)}" : | ||
| 160 | nil | ||
| 161 | }.compact.join(', ') | ||
| 162 | end | ||
| 163 | end | ||
| 164 | |||
| 165 | # Could there be more than one value related to the parent record? If so, | ||
| 166 | # then this will return true. If not, false. It's that simple. | ||
| 167 | # | ||
| 168 | def is_many? | ||
| 169 | associations.values.flatten.any? { |assoc| assoc.is_many? } | ||
| 170 | end | ||
| 171 | end | ||
| 172 | end | ||
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 | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/builder.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/builder.rb deleted file mode 100644 index dbd2ba0..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/builder.rb +++ /dev/null | |||
| @@ -1,264 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | class Index | ||
| 3 | # The Builder class is the core for the index definition block processing. | ||
| 4 | # There are four methods you really need to pay attention to: | ||
| 5 | # - indexes (aliased to includes and attribute) | ||
| 6 | # - has (aliased to attribute) | ||
| 7 | # - where | ||
| 8 | # - set_property (aliased to set_properties) | ||
| 9 | # | ||
| 10 | # The first two of these methods allow you to define what data makes up | ||
| 11 | # your indexes. #where provides a method to add manual SQL conditions, and | ||
| 12 | # set_property allows you to set some settings on a per-index basis. Check | ||
| 13 | # out each method's documentation for better ideas of usage. | ||
| 14 | # | ||
| 15 | class Builder | ||
| 16 | class << self | ||
| 17 | # No idea where this is coming from - haven't found it in any ruby or | ||
| 18 | # rails documentation. It's not needed though, so it gets undef'd. | ||
| 19 | # Hopefully the list of methods that get in the way doesn't get too | ||
| 20 | # long. | ||
| 21 | HiddenMethods = [:parent, :name, :id, :type].each { |method| | ||
| 22 | define_method(method) { | ||
| 23 | caller.grep(/irb.completion/).empty? ? method_missing(method) : super | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | attr_accessor :fields, :attributes, :properties, :conditions, | ||
| 28 | :groupings | ||
| 29 | |||
| 30 | # Set up all the collections. Consider this the equivalent of an | ||
| 31 | # instance's initialize method. | ||
| 32 | # | ||
| 33 | def setup | ||
| 34 | @fields = [] | ||
| 35 | @attributes = [] | ||
| 36 | @properties = {} | ||
| 37 | @conditions = [] | ||
| 38 | @groupings = [] | ||
| 39 | end | ||
| 40 | |||
| 41 | # This is how you add fields - the strings Sphinx looks at - to your | ||
| 42 | # index. Technically, to use this method, you need to pass in some | ||
| 43 | # columns and options - but there's some neat method_missing stuff | ||
| 44 | # happening, so lets stick to the expected syntax within a define_index | ||
| 45 | # block. | ||
| 46 | # | ||
| 47 | # Expected options are :as, which points to a column alias in symbol | ||
| 48 | # form, and :sortable, which indicates whether you want to sort by this | ||
| 49 | # field. | ||
| 50 | # | ||
| 51 | # Adding Single-Column Fields: | ||
| 52 | # | ||
| 53 | # You can use symbols or methods - and can chain methods together to | ||
| 54 | # get access down the associations tree. | ||
| 55 | # | ||
| 56 | # indexes :id, :as => :my_id | ||
| 57 | # indexes :name, :sortable => true | ||
| 58 | # indexes first_name, last_name, :sortable => true | ||
| 59 | # indexes users.posts.content, :as => :post_content | ||
| 60 | # indexes users(:id), :as => :user_ids | ||
| 61 | # | ||
| 62 | # Keep in mind that if any keywords for Ruby methods - such as id or | ||
| 63 | # name - clash with your column names, you need to use the symbol | ||
| 64 | # version (see the first, second and last examples above). | ||
| 65 | # | ||
| 66 | # If you specify multiple columns (example #2), a field will be created | ||
| 67 | # for each. Don't use the :as option in this case. If you want to merge | ||
| 68 | # those columns together, continue reading. | ||
| 69 | # | ||
| 70 | # Adding Multi-Column Fields: | ||
| 71 | # | ||
| 72 | # indexes [first_name, last_name], :as => :name | ||
| 73 | # indexes [location, parent.location], :as => :location | ||
| 74 | # | ||
| 75 | # To combine multiple columns into a single field, you need to wrap | ||
| 76 | # them in an Array, as shown by the above examples. There's no | ||
| 77 | # limitations on whether they're symbols or methods or what level of | ||
| 78 | # associations they come from. | ||
| 79 | # | ||
| 80 | # Adding SQL Fragment Fields | ||
| 81 | # | ||
| 82 | # You can also define a field using an SQL fragment, useful for when | ||
| 83 | # you would like to index a calculated value. | ||
| 84 | # | ||
| 85 | # indexes "age < 18", :as => :minor | ||
| 86 | # | ||
| 87 | def indexes(*args) | ||
| 88 | options = args.extract_options! | ||
| 89 | args.each do |columns| | ||
| 90 | field = Field.new(FauxColumn.coerce(columns), options) | ||
| 91 | fields << field | ||
| 92 | |||
| 93 | add_sort_attribute field, options if field.sortable | ||
| 94 | add_facet_attribute field, options if field.faceted | ||
| 95 | end | ||
| 96 | end | ||
| 97 | alias_method :field, :indexes | ||
| 98 | alias_method :includes, :indexes | ||
| 99 | |||
| 100 | # This is the method to add attributes to your index (hence why it is | ||
| 101 | # aliased as 'attribute'). The syntax is the same as #indexes, so use | ||
| 102 | # that as starting point, but keep in mind the following points. | ||
| 103 | # | ||
| 104 | # An attribute can have an alias (the :as option), but it is always | ||
| 105 | # sortable - so you don't need to explicitly request that. You _can_ | ||
| 106 | # specify the data type of the attribute (the :type option), but the | ||
| 107 | # code's pretty good at figuring that out itself from peering into the | ||
| 108 | # database. | ||
| 109 | # | ||
| 110 | # Attributes are limited to the following types: integers, floats, | ||
| 111 | # datetimes (converted to timestamps), booleans and strings. Don't | ||
| 112 | # forget that Sphinx converts string attributes to integers, which are | ||
| 113 | # useful for sorting, but that's about it. | ||
| 114 | # | ||
| 115 | # You can also have a collection of integers for multi-value attributes | ||
| 116 | # (MVAs). Generally these would be through a has_many relationship, | ||
| 117 | # like in this example: | ||
| 118 | # | ||
| 119 | # has posts(:id), :as => :post_ids | ||
| 120 | # | ||
| 121 | # This allows you to filter on any of the values tied to a specific | ||
| 122 | # record. Might be best to read through the Sphinx documentation to get | ||
| 123 | # a better idea of that though. | ||
| 124 | # | ||
| 125 | # Adding SQL Fragment Attributes | ||
| 126 | # | ||
| 127 | # You can also define an attribute using an SQL fragment, useful for | ||
| 128 | # when you would like to index a calculated value. Don't forget to set | ||
| 129 | # the type of the attribute though: | ||
| 130 | # | ||
| 131 | # has "age < 18", :as => :minor, :type => :boolean | ||
| 132 | # | ||
| 133 | # If you're creating attributes for latitude and longitude, don't | ||
| 134 | # forget that Sphinx expects these values to be in radians. | ||
| 135 | # | ||
| 136 | def has(*args) | ||
| 137 | options = args.extract_options! | ||
| 138 | args.each do |columns| | ||
| 139 | attribute = Attribute.new(FauxColumn.coerce(columns), options) | ||
| 140 | attributes << attribute | ||
| 141 | |||
| 142 | add_facet_attribute attribute, options if attribute.faceted | ||
| 143 | end | ||
| 144 | end | ||
| 145 | alias_method :attribute, :has | ||
| 146 | |||
| 147 | def facet(*args) | ||
| 148 | options = args.extract_options! | ||
| 149 | options[:facet] = true | ||
| 150 | |||
| 151 | args.each do |columns| | ||
| 152 | attribute = Attribute.new(FauxColumn.coerce(columns), options) | ||
| 153 | attributes << attribute | ||
| 154 | |||
| 155 | add_facet_attribute attribute, options | ||
| 156 | end | ||
| 157 | end | ||
| 158 | |||
| 159 | # Use this method to add some manual SQL conditions for your index | ||
| 160 | # request. You can pass in as many strings as you like, they'll get | ||
| 161 | # joined together with ANDs later on. | ||
| 162 | # | ||
| 163 | # where "user_id = 10" | ||
| 164 | # where "parent_type = 'Article'", "created_at < NOW()" | ||
| 165 | # | ||
| 166 | def where(*args) | ||
| 167 | @conditions += args | ||
| 168 | end | ||
| 169 | |||
| 170 | # Use this method to add some manual SQL strings to the GROUP BY | ||
| 171 | # clause. You can pass in as many strings as you'd like, they'll get | ||
| 172 | # joined together with commas later on. | ||
| 173 | # | ||
| 174 | # group_by "lat", "lng" | ||
| 175 | # | ||
| 176 | def group_by(*args) | ||
| 177 | @groupings += args | ||
| 178 | end | ||
| 179 | |||
| 180 | # This is what to use to set properties on the index. Chief amongst | ||
| 181 | # those is the delta property - to allow automatic updates to your | ||
| 182 | # indexes as new models are added and edited - but also you can | ||
| 183 | # define search-related properties which will be the defaults for all | ||
| 184 | # searches on the model. | ||
| 185 | # | ||
| 186 | # set_property :delta => true | ||
| 187 | # set_property :field_weights => {"name" => 100} | ||
| 188 | # set_property :order => "name ASC" | ||
| 189 | # set_property :include => :picture | ||
| 190 | # set_property :select => 'name' | ||
| 191 | # | ||
| 192 | # Also, the following two properties are particularly relevant for | ||
| 193 | # geo-location searching - latitude_attr and longitude_attr. If your | ||
| 194 | # attributes for these two values are named something other than | ||
| 195 | # lat/latitude or lon/long/longitude, you can dictate what they are | ||
| 196 | # when defining the index, so you don't need to specify them for every | ||
| 197 | # geo-related search. | ||
| 198 | # | ||
| 199 | # set_property :latitude_attr => "lt", :longitude_attr => "lg" | ||
| 200 | # | ||
| 201 | # Please don't forget to add a boolean field named 'delta' to your | ||
| 202 | # model's database table if enabling the delta index for it. | ||
| 203 | # Valid options for the delta property are: | ||
| 204 | # | ||
| 205 | # true | ||
| 206 | # false | ||
| 207 | # :default | ||
| 208 | # :delayed | ||
| 209 | # :datetime | ||
| 210 | # | ||
| 211 | # You can also extend ThinkingSphinx::Deltas::DefaultDelta to implement | ||
| 212 | # your own handling for delta indexing. | ||
| 213 | |||
| 214 | def set_property(*args) | ||
| 215 | options = args.extract_options! | ||
| 216 | if options.empty? | ||
| 217 | @properties[args[0]] = args[1] | ||
| 218 | else | ||
| 219 | @properties.merge!(options) | ||
| 220 | end | ||
| 221 | end | ||
| 222 | alias_method :set_properties, :set_property | ||
| 223 | |||
| 224 | # Handles the generation of new columns for the field and attribute | ||
| 225 | # definitions. | ||
| 226 | # | ||
| 227 | def method_missing(method, *args) | ||
| 228 | FauxColumn.new(method, *args) | ||
| 229 | end | ||
| 230 | |||
| 231 | # A method to allow adding fields from associations which have names | ||
| 232 | # that clash with method names in the Builder class (ie: properties, | ||
| 233 | # fields, attributes). | ||
| 234 | # | ||
| 235 | # Example: indexes assoc(:properties).column | ||
| 236 | # | ||
| 237 | def assoc(assoc, *args) | ||
| 238 | FauxColumn.new(assoc, *args) | ||
| 239 | end | ||
| 240 | |||
| 241 | private | ||
| 242 | |||
| 243 | def add_sort_attribute(field, options) | ||
| 244 | add_internal_attribute field, options, "_sort" | ||
| 245 | end | ||
| 246 | |||
| 247 | def add_facet_attribute(resource, options) | ||
| 248 | add_internal_attribute resource, options, "_facet", true | ||
| 249 | end | ||
| 250 | |||
| 251 | def add_internal_attribute(resource, options, suffix, crc = false) | ||
| 252 | @attributes << Attribute.new( | ||
| 253 | resource.columns.collect { |col| col.clone }, | ||
| 254 | options.merge( | ||
| 255 | :type => resource.is_a?(Field) ? :string : nil, | ||
| 256 | :as => resource.unique_name.to_s.concat(suffix).to_sym, | ||
| 257 | :crc => crc | ||
| 258 | ).except(:facet) | ||
| 259 | ) | ||
| 260 | end | ||
| 261 | end | ||
| 262 | end | ||
| 263 | end | ||
| 264 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/faux_column.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/faux_column.rb deleted file mode 100644 index 84068de..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/faux_column.rb +++ /dev/null | |||
| @@ -1,110 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | class Index | ||
| 3 | # Instances of this class represent database columns and the stack of | ||
| 4 | # associations that lead from the base model to them. | ||
| 5 | # | ||
| 6 | # The name and stack are accessible through methods starting with __ to | ||
| 7 | # avoid conflicting with the method_missing calls that build the stack. | ||
| 8 | # | ||
| 9 | class FauxColumn | ||
| 10 | # Create a new column with a pre-defined stack. The top element in the | ||
| 11 | # stack will get shifted to be the name value. | ||
| 12 | # | ||
| 13 | def initialize(*stack) | ||
| 14 | @name = stack.pop | ||
| 15 | @stack = stack | ||
| 16 | end | ||
| 17 | |||
| 18 | def self.coerce(columns) | ||
| 19 | case columns | ||
| 20 | when Symbol, String | ||
| 21 | FauxColumn.new(columns) | ||
| 22 | when Array | ||
| 23 | columns.collect { |col| FauxColumn.coerce(col) } | ||
| 24 | when FauxColumn | ||
| 25 | columns | ||
| 26 | else | ||
| 27 | nil | ||
| 28 | end | ||
| 29 | end | ||
| 30 | |||
| 31 | # Can't use normal method name, as that could be an association or | ||
| 32 | # column name. | ||
| 33 | # | ||
| 34 | def __name | ||
| 35 | @name | ||
| 36 | end | ||
| 37 | |||
| 38 | # Can't use normal method name, as that could be an association or | ||
| 39 | # column name. | ||
| 40 | # | ||
| 41 | def __stack | ||
| 42 | @stack | ||
| 43 | end | ||
| 44 | |||
| 45 | # Returns true if the stack is empty *and* if the name is a string - | ||
| 46 | # which is an indication that of raw SQL, as opposed to a value from a | ||
| 47 | # table's column. | ||
| 48 | # | ||
| 49 | def is_string? | ||
| 50 | @name.is_a?(String) && @stack.empty? | ||
| 51 | end | ||
| 52 | |||
| 53 | # This handles any 'invalid' method calls and sets them as the name, | ||
| 54 | # and pushing the previous name into the stack. The object returns | ||
| 55 | # itself. | ||
| 56 | # | ||
| 57 | # If there's a single argument, it becomes the name, and the method | ||
| 58 | # symbol goes into the stack as well. Multiple arguments means new | ||
| 59 | # columns with the original stack and new names (from each argument) gets | ||
| 60 | # returned. | ||
| 61 | # | ||
| 62 | # Easier to explain with examples: | ||
| 63 | # | ||
| 64 | # col = FauxColumn.new :a, :b, :c | ||
| 65 | # col.__name #=> :c | ||
| 66 | # col.__stack #=> [:a, :b] | ||
| 67 | # | ||
| 68 | # col.whatever #=> col | ||
| 69 | # col.__name #=> :whatever | ||
| 70 | # col.__stack #=> [:a, :b, :c] | ||
| 71 | # | ||
| 72 | # col.something(:id) #=> col | ||
| 73 | # col.__name #=> :id | ||
| 74 | # col.__stack #=> [:a, :b, :c, :whatever, :something] | ||
| 75 | # | ||
| 76 | # cols = col.short(:x, :y, :z) | ||
| 77 | # cols[0].__name #=> :x | ||
| 78 | # cols[0].__stack #=> [:a, :b, :c, :whatever, :something, :short] | ||
| 79 | # cols[1].__name #=> :y | ||
| 80 | # cols[1].__stack #=> [:a, :b, :c, :whatever, :something, :short] | ||
| 81 | # cols[2].__name #=> :z | ||
| 82 | # cols[2].__stack #=> [:a, :b, :c, :whatever, :something, :short] | ||
| 83 | # | ||
| 84 | # Also, this allows method chaining to build up a relevant stack: | ||
| 85 | # | ||
| 86 | # col = FauxColumn.new :a, :b | ||
| 87 | # col.__name #=> :b | ||
| 88 | # col.__stack #=> [:a] | ||
| 89 | # | ||
| 90 | # col.one.two.three #=> col | ||
| 91 | # col.__name #=> :three | ||
| 92 | # col.__stack #=> [:a, :b, :one, :two] | ||
| 93 | # | ||
| 94 | def method_missing(method, *args) | ||
| 95 | @stack << @name | ||
| 96 | @name = method | ||
| 97 | |||
| 98 | if (args.empty?) | ||
| 99 | self | ||
| 100 | elsif (args.length == 1) | ||
| 101 | method_missing(args.first) | ||
| 102 | else | ||
| 103 | args.collect { |arg| | ||
| 104 | FauxColumn.new(@stack + [@name, arg]) | ||
| 105 | } | ||
| 106 | end | ||
| 107 | end | ||
| 108 | end | ||
| 109 | end | ||
| 110 | end \ No newline at end of file | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/rails_additions.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/rails_additions.rb deleted file mode 100644 index c7db0a1..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/rails_additions.rb +++ /dev/null | |||
| @@ -1,136 +0,0 @@ | |||
| 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") 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 | ||
| 59 | end | ||
| 60 | |||
| 61 | module ThinkingSphinx | ||
| 62 | module ActiveRecordQuotedName | ||
| 63 | def quoted_table_name | ||
| 64 | self.connection.quote_table_name(self.table_name) | ||
| 65 | end | ||
| 66 | end | ||
| 67 | end | ||
| 68 | |||
| 69 | ActiveRecord::Base.extend( | ||
| 70 | ThinkingSphinx::ActiveRecordQuotedName | ||
| 71 | ) unless ActiveRecord::Base.respond_to?("quoted_table_name") | ||
| 72 | |||
| 73 | module ThinkingSphinx | ||
| 74 | module ActiveRecordStoreFullSTIClass | ||
| 75 | def store_full_sti_class | ||
| 76 | false | ||
| 77 | end | ||
| 78 | end | ||
| 79 | end | ||
| 80 | |||
| 81 | ActiveRecord::Base.extend( | ||
| 82 | ThinkingSphinx::ActiveRecordStoreFullSTIClass | ||
| 83 | ) unless ActiveRecord::Base.respond_to?(:store_full_sti_class) | ||
| 84 | |||
| 85 | module ThinkingSphinx | ||
| 86 | module ClassAttributeMethods | ||
| 87 | def cattr_reader(*syms) | ||
| 88 | syms.flatten.each do |sym| | ||
| 89 | next if sym.is_a?(Hash) | ||
| 90 | class_eval(<<-EOS, __FILE__, __LINE__) | ||
| 91 | unless defined? @@#{sym} | ||
| 92 | @@#{sym} = nil | ||
| 93 | end | ||
| 94 | |||
| 95 | def self.#{sym} | ||
| 96 | @@#{sym} | ||
| 97 | end | ||
| 98 | |||
| 99 | def #{sym} | ||
| 100 | @@#{sym} | ||
| 101 | end | ||
| 102 | EOS | ||
| 103 | end | ||
| 104 | end | ||
| 105 | |||
| 106 | def cattr_writer(*syms) | ||
| 107 | options = syms.extract_options! | ||
| 108 | syms.flatten.each do |sym| | ||
| 109 | class_eval(<<-EOS, __FILE__, __LINE__) | ||
| 110 | unless defined? @@#{sym} | ||
| 111 | @@#{sym} = nil | ||
| 112 | end | ||
| 113 | |||
| 114 | def self.#{sym}=(obj) | ||
| 115 | @@#{sym} = obj | ||
| 116 | end | ||
| 117 | |||
| 118 | #{" | ||
| 119 | def #{sym}=(obj) | ||
| 120 | @@#{sym} = obj | ||
| 121 | end | ||
| 122 | " unless options[:instance_writer] == false } | ||
| 123 | EOS | ||
| 124 | end | ||
| 125 | end | ||
| 126 | |||
| 127 | def cattr_accessor(*syms) | ||
| 128 | cattr_reader(*syms) | ||
| 129 | cattr_writer(*syms) | ||
| 130 | end | ||
| 131 | end | ||
| 132 | end | ||
| 133 | |||
| 134 | Class.extend( | ||
| 135 | ThinkingSphinx::ClassAttributeMethods | ||
| 136 | ) unless Class.respond_to?(:cattr_reader) | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/search.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/search.rb deleted file mode 100644 index d476787..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/search.rb +++ /dev/null | |||
| @@ -1,780 +0,0 @@ | |||
| 1 | module ThinkingSphinx | ||
| 2 | # Once you've got those indexes in and built, this is the stuff that | ||
| 3 | # matters - how to search! This class provides a generic search | ||
| 4 | # interface - which you can use to search all your indexed models at once. | ||
| 5 | # Most times, you will just want a specific model's results - to search and | ||
| 6 | # search_for_ids methods will do the job in exactly the same manner when | ||
| 7 | # called from a model. | ||
| 8 | # | ||
| 9 | class Search | ||
| 10 | GlobalFacetOptions = { | ||
| 11 | :all_attributes => false, | ||
| 12 | :class_facet => true | ||
| 13 | } | ||
| 14 | |||
| 15 | class << self | ||
| 16 | # Searches for results that match the parameters provided. Will only | ||
| 17 | # return the ids for the matching objects. See #search for syntax | ||
| 18 | # examples. | ||
| 19 | # | ||
| 20 | # Note that this only searches the Sphinx index, with no ActiveRecord | ||
| 21 | # queries. Thus, if your index is not in sync with the database, this | ||
| 22 | # method may return ids that no longer exist there. | ||
| 23 | # | ||
| 24 | def search_for_ids(*args) | ||
| 25 | results, client = search_results(*args.clone) | ||
| 26 | |||
| 27 | options = args.extract_options! | ||
| 28 | page = options[:page] ? options[:page].to_i : 1 | ||
| 29 | |||
| 30 | ThinkingSphinx::Collection.ids_from_results(results, page, client.limit, options) | ||
| 31 | end | ||
| 32 | |||
| 33 | # Searches through the Sphinx indexes for relevant matches. There's | ||
| 34 | # various ways to search, sort, group and filter - which are covered | ||
| 35 | # below. | ||
| 36 | # | ||
| 37 | # Also, if you have WillPaginate installed, the search method can be used | ||
| 38 | # just like paginate. The same parameters - :page and :per_page - work as | ||
| 39 | # expected, and the returned result set can be used by the will_paginate | ||
| 40 | # helper. | ||
| 41 | # | ||
| 42 | # == Basic Searching | ||
| 43 | # | ||
| 44 | # The simplest way of searching is straight text. | ||
| 45 | # | ||
| 46 | # ThinkingSphinx::Search.search "pat" | ||
| 47 | # ThinkingSphinx::Search.search "google" | ||
| 48 | # User.search "pat", :page => (params[:page] || 1) | ||
| 49 | # Article.search "relevant news issue of the day" | ||
| 50 | # | ||
| 51 | # If you specify :include, like in an #find call, this will be respected | ||
| 52 | # when loading the relevant models from the search results. | ||
| 53 | # | ||
| 54 | # User.search "pat", :include => :posts | ||
| 55 | # | ||
| 56 | # == Match Modes | ||
| 57 | # | ||
| 58 | # Sphinx supports 5 different matching modes. By default Thinking Sphinx | ||
| 59 | # uses :all, which unsurprisingly requires all the supplied search terms | ||
| 60 | # to match a result. | ||
| 61 | # | ||
| 62 | # Alternative modes include: | ||
| 63 | # | ||
| 64 | # User.search "pat allan", :match_mode => :any | ||
| 65 | # User.search "pat allan", :match_mode => :phrase | ||
| 66 | # User.search "pat | allan", :match_mode => :boolean | ||
| 67 | # User.search "@name pat | @username pat", :match_mode => :extended | ||
| 68 | # | ||
| 69 | # Any will find results with any of the search terms. Phrase treats the search | ||
| 70 | # terms a single phrase instead of individual words. Boolean and extended allow | ||
| 71 | # for more complex query syntax, refer to the sphinx documentation for further | ||
| 72 | # details. | ||
| 73 | # | ||
| 74 | # == Weighting | ||
| 75 | # | ||
| 76 | # Sphinx has support for weighting, where matches in one field can be considered | ||
| 77 | # more important than in another. Weights are integers, with 1 as the default. | ||
| 78 | # They can be set per-search like this: | ||
| 79 | # | ||
| 80 | # User.search "pat allan", :field_weights => { :alias => 4, :aka => 2 } | ||
| 81 | # | ||
| 82 | # If you're searching multiple models, you can set per-index weights: | ||
| 83 | # | ||
| 84 | # ThinkingSphinx::Search.search "pat", :index_weights => { User => 10 } | ||
| 85 | # | ||
| 86 | # See http://sphinxsearch.com/doc.html#weighting for further details. | ||
| 87 | # | ||
| 88 | # == Searching by Fields | ||
| 89 | # | ||
| 90 | # If you want to step it up a level, you can limit your search terms to | ||
| 91 | # specific fields: | ||
| 92 | # | ||
| 93 | # User.search :conditions => {:name => "pat"} | ||
| 94 | # | ||
| 95 | # This uses Sphinx's extended match mode, unless you specify a different | ||
| 96 | # match mode explicitly (but then this way of searching won't work). Also | ||
| 97 | # note that you don't need to put in a search string. | ||
| 98 | # | ||
| 99 | # == Searching by Attributes | ||
| 100 | # | ||
| 101 | # Also known as filters, you can limit your searches to documents that | ||
| 102 | # have specific values for their attributes. There are three ways to do | ||
| 103 | # this. The first two techniques work in all scenarios - using the :with | ||
| 104 | # or :with_all options. | ||
| 105 | # | ||
| 106 | # ThinkingSphinx::Search.search :with => {:tag_ids => 10} | ||
| 107 | # ThinkingSphinx::Search.search :with => {:tag_ids => [10,12]} | ||
| 108 | # ThinkingSphinx::Search.search :with_all => {:tag_ids => [10,12]} | ||
| 109 | # | ||
| 110 | # The first :with search will match records with a tag_id attribute of 10. | ||
| 111 | # The second :with will match records with a tag_id attribute of 10 OR 12. | ||
| 112 | # If you need to find records that are tagged with ids 10 AND 12, you | ||
| 113 | # will need to use the :with_all search parameter. This is particuarly | ||
| 114 | # useful in conjunction with Multi Value Attributes (MVAs). | ||
| 115 | # | ||
| 116 | # The third filtering technique is only viable if you're searching with a | ||
| 117 | # specific model (not multi-model searching). With a single model, | ||
| 118 | # Thinking Sphinx can figure out what attributes and fields are available, | ||
| 119 | # so you can put it all in the :conditions hash, and it will sort it out. | ||
| 120 | # | ||
| 121 | # Node.search :conditions => {:parent_id => 10} | ||
| 122 | # | ||
| 123 | # Filters can be single values, arrays of values, or ranges. | ||
| 124 | # | ||
| 125 | # Article.search "East Timor", :conditions => {:rating => 3..5} | ||
| 126 | # | ||
| 127 | # == Excluding by Attributes | ||
| 128 | # | ||
| 129 | # Sphinx also supports negative filtering - where the filters are of | ||
| 130 | # attribute values to exclude. This is done with the :without option: | ||
| 131 | # | ||
| 132 | # User.search :without => {:role_id => 1} | ||
| 133 | # | ||
| 134 | # == Excluding by Primary Key | ||
| 135 | # | ||
| 136 | # There is a shortcut to exclude records by their ActiveRecord primary key: | ||
| 137 | # | ||
| 138 | # User.search :without_ids => 1 | ||
| 139 | # | ||
| 140 | # Pass an array or a single value. | ||
| 141 | # | ||
| 142 | # The primary key must be an integer as a negative filter is used. Note | ||
| 143 | # that for multi-model search, an id may occur in more than one model. | ||
| 144 | # | ||
| 145 | # == Infix (Star) Searching | ||
| 146 | # | ||
| 147 | # By default, Sphinx uses English stemming, e.g. matching "shoes" if you | ||
| 148 | # search for "shoe". It won't find "Melbourne" if you search for | ||
| 149 | # "elbourn", though. | ||
| 150 | # | ||
| 151 | # Enable infix searching by something like this in config/sphinx.yml: | ||
| 152 | # | ||
| 153 | # development: | ||
| 154 | # enable_star: 1 | ||
| 155 | # min_infix_length: 2 | ||
| 156 | # | ||
| 157 | # Note that this will make indexing take longer. | ||
| 158 | # | ||
| 159 | # With those settings (and after reindexing), wildcard asterisks can be used | ||
| 160 | # in queries: | ||
| 161 | # | ||
| 162 | # Location.search "*elbourn*" | ||
| 163 | # | ||
| 164 | # To automatically add asterisks around every token (but not operators), | ||
| 165 | # pass the :star option: | ||
| 166 | # | ||
| 167 | # Location.search "elbourn -ustrali", :star => true, :match_mode => :boolean | ||
| 168 | # | ||
| 169 | # This would become "*elbourn* -*ustrali*". The :star option only adds the | ||
| 170 | # asterisks. You need to make the config/sphinx.yml changes yourself. | ||
| 171 | # | ||
| 172 | # By default, the tokens are assumed to match the regular expression /\w+/u. | ||
| 173 | # If you've modified the charset_table, pass another regular expression, e.g. | ||
| 174 | # | ||
| 175 | # User.search("oo@bar.c", :star => /[\w@.]+/u) | ||
| 176 | # | ||
| 177 | # to search for "*oo@bar.c*" and not "*oo*@*bar*.*c*". | ||
| 178 | # | ||
| 179 | # == Sorting | ||
| 180 | # | ||
| 181 | # Sphinx can only sort by attributes, so generally you will need to avoid | ||
| 182 | # using field names in your :order option. However, if you're searching | ||
| 183 | # on a single model, and have specified some fields as sortable, you can | ||
| 184 | # use those field names and Thinking Sphinx will interpret accordingly. | ||
| 185 | # Remember: this will only happen for single-model searches, and only | ||
| 186 | # through the :order option. | ||
| 187 | # | ||
| 188 | # Location.search "Melbourne", :order => :state | ||
| 189 | # User.search :conditions => {:role_id => 2}, :order => "name ASC" | ||
| 190 | # | ||
| 191 | # Keep in mind that if you use a string, you *must* specify the direction | ||
| 192 | # (ASC or DESC) else Sphinx won't return any results. If you use a symbol | ||
| 193 | # then Thinking Sphinx assumes ASC, but if you wish to state otherwise, | ||
| 194 | # use the :sort_mode option: | ||
| 195 | # | ||
| 196 | # Location.search "Melbourne", :order => :state, :sort_mode => :desc | ||
| 197 | # | ||
| 198 | # Of course, there are other sort modes - check out the Sphinx | ||
| 199 | # documentation[http://sphinxsearch.com/doc.html] for that level of | ||
| 200 | # detail though. | ||
| 201 | # | ||
| 202 | # If desired, you can sort by a column in your model instead of a sphinx | ||
| 203 | # field or attribute. This sort only applies to the current page, so is | ||
| 204 | # most useful when performing a search with a single page of results. | ||
| 205 | # | ||
| 206 | # User.search("pat", :sql_order => "name") | ||
| 207 | # | ||
| 208 | # == Grouping | ||
| 209 | # | ||
| 210 | # For this you can use the group_by, group_clause and group_function | ||
| 211 | # options - which are all directly linked to Sphinx's expectations. No | ||
| 212 | # magic from Thinking Sphinx. It can get a little tricky, so make sure | ||
| 213 | # you read all the relevant | ||
| 214 | # documentation[http://sphinxsearch.com/doc.html#clustering] first. | ||
| 215 | # | ||
| 216 | # Grouping is done via three parameters within the options hash | ||
| 217 | # * <tt>:group_function</tt> determines the way grouping is done | ||
| 218 | # * <tt>:group_by</tt> determines the field which is used for grouping | ||
| 219 | # * <tt>:group_clause</tt> determines the sorting order | ||
| 220 | # | ||
| 221 | # === group_function | ||
| 222 | # | ||
| 223 | # Valid values for :group_function are | ||
| 224 | # * <tt>:day</tt>, <tt>:week</tt>, <tt>:month</tt>, <tt>:year</tt> - Grouping is done by the respective timeframes. | ||
| 225 | # * <tt>:attr</tt>, <tt>:attrpair</tt> - Grouping is done by the specified attributes(s) | ||
| 226 | # | ||
| 227 | # === group_by | ||
| 228 | # | ||
| 229 | # This parameter denotes the field by which grouping is done. Note that the | ||
| 230 | # specified field must be a sphinx attribute or index. | ||
| 231 | # | ||
| 232 | # === group_clause | ||
| 233 | # | ||
| 234 | # This determines the sorting order of the groups. In a grouping search, | ||
| 235 | # the matches within a group will sorted by the <tt>:sort_mode</tt> and <tt>:order</tt> parameters. | ||
| 236 | # The group matches themselves however, will be sorted by <tt>:group_clause</tt>. | ||
| 237 | # | ||
| 238 | # The syntax for this is the same as an order parameter in extended sort mode. | ||
| 239 | # Namely, you can specify an SQL-like sort expression with up to 5 attributes | ||
| 240 | # (including internal attributes), eg: "@relevance DESC, price ASC, @id DESC" | ||
| 241 | # | ||
| 242 | # === Grouping by timestamp | ||
| 243 | # | ||
| 244 | # Timestamp grouping groups off items by the day, week, month or year of the | ||
| 245 | # attribute given. In order to do this you need to define a timestamp attribute, | ||
| 246 | # which pretty much looks like the standard defintion for any attribute. | ||
| 247 | # | ||
| 248 | # define_index do | ||
| 249 | # # | ||
| 250 | # # All your other stuff | ||
| 251 | # # | ||
| 252 | # has :created_at | ||
| 253 | # end | ||
| 254 | # | ||
| 255 | # When you need to fire off your search, it'll go something to the tune of | ||
| 256 | # | ||
| 257 | # Fruit.search "apricot", :group_function => :day, :group_by => 'created_at' | ||
| 258 | # | ||
| 259 | # The <tt>@groupby</tt> special attribute will contain the date for that group. | ||
| 260 | # Depending on the <tt>:group_function</tt> parameter, the date format will be | ||
| 261 | # | ||
| 262 | # * <tt>:day</tt> - YYYYMMDD | ||
| 263 | # * <tt>:week</tt> - YYYYNNN (NNN is the first day of the week in question, | ||
| 264 | # counting from the start of the year ) | ||
| 265 | # * <tt>:month</tt> - YYYYMM | ||
| 266 | # * <tt>:year</tt> - YYYY | ||
| 267 | # | ||
| 268 | # | ||
| 269 | # === Grouping by attribute | ||
| 270 | # | ||
| 271 | # The syntax is the same as grouping by timestamp, except for the fact that the | ||
| 272 | # <tt>:group_function</tt> parameter is changed | ||
| 273 | # | ||
| 274 | # Fruit.search "apricot", :group_function => :attr, :group_by => 'size' | ||
| 275 | # | ||
| 276 | # | ||
| 277 | # == Geo/Location Searching | ||
| 278 | # | ||
| 279 | # Sphinx - and therefore Thinking Sphinx - has the facility to search | ||
| 280 | # around a geographical point, using a given latitude and longitude. To | ||
| 281 | # take advantage of this, you will need to have both of those values in | ||
| 282 | # attributes. To search with that point, you can then use one of the | ||
| 283 | # following syntax examples: | ||
| 284 | # | ||
| 285 | # Address.search "Melbourne", :geo => [1.4, -2.217], :order => "@geodist asc" | ||
| 286 | # Address.search "Australia", :geo => [-0.55, 3.108], :order => "@geodist asc" | ||
| 287 | # :latitude_attr => "latit", :longitude_attr => "longit" | ||
| 288 | # | ||
| 289 | # The first example applies when your latitude and longitude attributes | ||
| 290 | # are named any of lat, latitude, lon, long or longitude. If that's not | ||
| 291 | # the case, you will need to explicitly state them in your search, _or_ | ||
| 292 | # you can do so in your model: | ||
| 293 | # | ||
| 294 | # define_index do | ||
| 295 | # has :latit # Float column, stored in radians | ||
| 296 | # has :longit # Float column, stored in radians | ||
| 297 | # | ||
| 298 | # set_property :latitude_attr => "latit" | ||
| 299 | # set_property :longitude_attr => "longit" | ||
| 300 | # end | ||
| 301 | # | ||
| 302 | # Now, geo-location searching really only has an affect if you have a | ||
| 303 | # filter, sort or grouping clause related to it - otherwise it's just a | ||
| 304 | # normal search, and _will not_ return a distance value otherwise. To | ||
| 305 | # make use of the positioning difference, use the special attribute | ||
| 306 | # "@geodist" in any of your filters or sorting or grouping clauses. | ||
| 307 | # | ||
| 308 | # And don't forget - both the latitude and longitude you use in your | ||
| 309 | # search, and the values in your indexes, need to be stored as a float in radians, | ||
| 310 | # _not_ degrees. Keep in mind that if you do this conversion in SQL | ||
| 311 | # you will need to explicitly declare a column type of :float. | ||
| 312 | # | ||
| 313 | # define_index do | ||
| 314 | # has 'RADIANS(lat)', :as => :lat, :type => :float | ||
| 315 | # # ... | ||
| 316 | # end | ||
| 317 | # | ||
| 318 | # Once you've got your results set, you can access the distances as | ||
| 319 | # follows: | ||
| 320 | # | ||
| 321 | # @results.each_with_geodist do |result, distance| | ||
| 322 | # # ... | ||
| 323 | # end | ||
| 324 | # | ||
| 325 | # The distance value is returned as a float, representing the distance in | ||
| 326 | # metres. | ||
| 327 | # | ||
| 328 | # == Handling a Stale Index | ||
| 329 | # | ||
| 330 | # Especially if you don't use delta indexing, you risk having records in the | ||
| 331 | # Sphinx index that are no longer in the database. By default, those will simply | ||
| 332 | # come back as nils: | ||
| 333 | # | ||
| 334 | # >> pat_user.delete | ||
| 335 | # >> User.search("pat") | ||
| 336 | # Sphinx Result: [1,2] | ||
| 337 | # => [nil, <#User id: 2>] | ||
| 338 | # | ||
| 339 | # (If you search across multiple models, you'll get ActiveRecord::RecordNotFound.) | ||
| 340 | # | ||
| 341 | # You can simply Array#compact these results or handle the nils in some other way, but | ||
| 342 | # Sphinx will still report two results, and the missing records may upset your layout. | ||
| 343 | # | ||
| 344 | # If you pass :retry_stale => true to a single-model search, missing records will | ||
| 345 | # cause Thinking Sphinx to retry the query but excluding those records. Since search | ||
| 346 | # is paginated, the new search could potentially include missing records as well, so by | ||
| 347 | # default Thinking Sphinx will retry three times. Pass :retry_stale => 5 to retry five | ||
| 348 | # times, and so on. If there are still missing ids on the last retry, they are | ||
| 349 | # shown as nils. | ||
| 350 | # | ||
| 351 | def search(*args) | ||
| 352 | query = args.clone # an array | ||
| 353 | options = query.extract_options! | ||
| 354 | |||
| 355 | retry_search_on_stale_index(query, options) do | ||
| 356 | results, client = search_results(*(query + [options])) | ||
| 357 | |||
| 358 | ::ActiveRecord::Base.logger.error( | ||
| 359 | "Sphinx Error: #{results[:error]}" | ||
| 360 | ) if results[:error] | ||
| 361 | |||
| 362 | klass = options[:class] | ||
| 363 | page = options[:page] ? options[:page].to_i : 1 | ||
| 364 | |||
| 365 | ThinkingSphinx::Collection.create_from_results(results, page, client.limit, options) | ||
| 366 | end | ||
| 367 | end | ||
| 368 | |||
| 369 | def retry_search_on_stale_index(query, options, &block) | ||
| 370 | stale_ids = [] | ||
| 371 | stale_retries_left = case options[:retry_stale] | ||
| 372 | when true | ||
| 373 | 3 # default to three retries | ||
| 374 | when nil, false | ||
| 375 | 0 # no retries | ||
| 376 | else options[:retry_stale].to_i | ||
| 377 | end | ||
| 378 | begin | ||
| 379 | # Passing this in an option so Collection.create_from_results can see it. | ||
| 380 | # It should only raise on stale records if there are any retries left. | ||
| 381 | options[:raise_on_stale] = stale_retries_left > 0 | ||
| 382 | block.call | ||
| 383 | # If ThinkingSphinx::Collection.create_from_results found records in Sphinx but not | ||
| 384 | # in the DB and the :raise_on_stale option is set, this exception is raised. We retry | ||
| 385 | # a limited number of times, excluding the stale ids from the search. | ||
| 386 | rescue StaleIdsException => e | ||
| 387 | stale_retries_left -= 1 | ||
| 388 | |||
| 389 | stale_ids |= e.ids # For logging | ||
| 390 | options[:without_ids] = Array(options[:without_ids]) | e.ids # Actual exclusion | ||
| 391 | |||
| 392 | tries = stale_retries_left | ||
| 393 | ::ActiveRecord::Base.logger.debug("Sphinx Stale Ids (%s %s left): %s" % [ | ||
| 394 | tries, (tries==1 ? 'try' : 'tries'), stale_ids.join(', ') | ||
| 395 | ]) | ||
| 396 | |||
| 397 | retry | ||
| 398 | end | ||
| 399 | end | ||
| 400 | |||
| 401 | def count(*args) | ||
| 402 | results, client = search_results(*args.clone) | ||
| 403 | results[:total_found] || 0 | ||
| 404 | end | ||
| 405 | |||
| 406 | # Checks if a document with the given id exists within a specific index. | ||
| 407 | # Expected parameters: | ||
| 408 | # | ||
| 409 | # - ID of the document | ||
| 410 | # - Index to check within | ||
| 411 | # - Options hash (defaults to {}) | ||
| 412 | # | ||
| 413 | # Example: | ||
| 414 | # | ||
| 415 | # ThinkingSphinx::Search.search_for_id(10, "user_core", :class => User) | ||
| 416 | # | ||
| 417 | def search_for_id(*args) | ||
| 418 | options = args.extract_options! | ||
| 419 | client = client_from_options options | ||
| 420 | |||
| 421 | query, filters = search_conditions( | ||
| 422 | options[:class], options[:conditions] || {} | ||
| 423 | ) | ||
| 424 | client.filters += filters | ||
| 425 | client.match_mode = :extended unless query.empty? | ||
| 426 | client.id_range = args.first..args.first | ||
| 427 | |||
| 428 | begin | ||
| 429 | return client.query(query, args[1])[:matches].length > 0 | ||
| 430 | rescue Errno::ECONNREFUSED => err | ||
| 431 | raise ThinkingSphinx::ConnectionError, "Connection to Sphinx Daemon (searchd) failed." | ||
| 432 | end | ||
| 433 | end | ||
| 434 | |||
| 435 | # Model.facets *args | ||
| 436 | # ThinkingSphinx::Search.facets *args | ||
| 437 | # ThinkingSphinx::Search.facets *args, :all_attributes => true | ||
| 438 | # ThinkingSphinx::Search.facets *args, :class_facet => false | ||
| 439 | # | ||
| 440 | def facets(*args) | ||
| 441 | options = args.extract_options! | ||
| 442 | |||
| 443 | if options[:class] | ||
| 444 | facets_for_model options[:class], args, options | ||
| 445 | else | ||
| 446 | facets_for_all_models args, options | ||
| 447 | end | ||
| 448 | end | ||
| 449 | |||
| 450 | private | ||
| 451 | |||
| 452 | # This method handles the common search functionality, and returns both | ||
| 453 | # the result hash and the client. Not super elegant, but it'll do for | ||
| 454 | # the moment. | ||
| 455 | # | ||
| 456 | def search_results(*args) | ||
| 457 | options = args.extract_options! | ||
| 458 | query = args.join(' ') | ||
| 459 | client = client_from_options options | ||
| 460 | |||
| 461 | query = star_query(query, options[:star]) if options[:star] | ||
| 462 | |||
| 463 | extra_query, filters = search_conditions( | ||
| 464 | options[:class], options[:conditions] || {} | ||
| 465 | ) | ||
| 466 | client.filters += filters | ||
| 467 | client.match_mode = :extended unless extra_query.empty? | ||
| 468 | query = [query, extra_query].join(' ') | ||
| 469 | query.strip! # Because "" and " " are not equivalent | ||
| 470 | |||
| 471 | set_sort_options! client, options | ||
| 472 | |||
| 473 | client.limit = options[:per_page].to_i if options[:per_page] | ||
| 474 | page = options[:page] ? options[:page].to_i : 1 | ||
| 475 | page = 1 if page <= 0 | ||
| 476 | client.offset = (page - 1) * client.limit | ||
| 477 | |||
| 478 | begin | ||
| 479 | ::ActiveRecord::Base.logger.debug "Sphinx: #{query}" | ||
| 480 | results = client.query query | ||
| 481 | ::ActiveRecord::Base.logger.debug "Sphinx Result: #{results[:matches].collect{|m| m[:attributes]["sphinx_internal_id"]}.inspect}" | ||
| 482 | rescue Errno::ECONNREFUSED => err | ||
| 483 | raise ThinkingSphinx::ConnectionError, "Connection to Sphinx Daemon (searchd) failed." | ||
| 484 | end | ||
| 485 | |||
| 486 | return results, client | ||
| 487 | end | ||
| 488 | |||
| 489 | # Set all the appropriate settings for the client, using the provided | ||
| 490 | # options hash. | ||
| 491 | # | ||
| 492 | def client_from_options(options = {}) | ||
| 493 | config = ThinkingSphinx::Configuration.instance | ||
| 494 | client = Riddle::Client.new config.address, config.port | ||
| 495 | klass = options[:class] | ||
| 496 | index_options = klass ? klass.sphinx_index_options : {} | ||
| 497 | |||
| 498 | # The Riddle default is per-query max_matches=1000. If we set the | ||
| 499 | # per-server max to a smaller value in sphinx.yml, we need to override | ||
| 500 | # the Riddle default or else we get search errors like | ||
| 501 | # "per-query max_matches=1000 out of bounds (per-server max_matches=200)" | ||
| 502 | if per_server_max_matches = config.configuration.searchd.max_matches | ||
| 503 | options[:max_matches] ||= per_server_max_matches | ||
| 504 | end | ||
| 505 | |||
| 506 | # Turn :index_weights => { "foo" => 2, User => 1 } | ||
| 507 | # into :index_weights => { "foo" => 2, "user_core" => 1, "user_delta" => 1 } | ||
| 508 | if iw = options[:index_weights] | ||
| 509 | options[:index_weights] = iw.inject({}) do |hash, (index,weight)| | ||
| 510 | if index.is_a?(Class) | ||
| 511 | name = ThinkingSphinx::Index.name(index) | ||
| 512 | hash["#{name}_core"] = weight | ||
| 513 | hash["#{name}_delta"] = weight | ||
| 514 | else | ||
| 515 | hash[index] = weight | ||
| 516 | end | ||
| 517 | hash | ||
| 518 | end | ||
| 519 | end | ||
| 520 | |||
| 521 | [ | ||
| 522 | :max_matches, :match_mode, :sort_mode, :sort_by, :id_range, | ||
| 523 | :group_by, :group_function, :group_clause, :group_distinct, :cut_off, | ||
| 524 | :retry_count, :retry_delay, :index_weights, :rank_mode, | ||
| 525 | :max_query_time, :field_weights, :filters, :anchor, :limit | ||
| 526 | ].each do |key| | ||
| 527 | client.send( | ||
| 528 | key.to_s.concat("=").to_sym, | ||
| 529 | options[key] || index_options[key] || client.send(key) | ||
| 530 | ) | ||
| 531 | end | ||
| 532 | |||
| 533 | options[:classes] = [klass] if klass | ||
| 534 | |||
| 535 | client.anchor = anchor_conditions(klass, options) || {} if client.anchor.empty? | ||
| 536 | |||
| 537 | client.filters << Riddle::Client::Filter.new( | ||
| 538 | "sphinx_deleted", [0] | ||
| 539 | ) | ||
| 540 | |||
| 541 | # class filters | ||
| 542 | client.filters << Riddle::Client::Filter.new( | ||
| 543 | "class_crc", options[:classes].collect { |k| k.to_crc32s }.flatten | ||
| 544 | ) if options[:classes] | ||
| 545 | |||
| 546 | # normal attribute filters | ||
| 547 | client.filters += options[:with].collect { |attr,val| | ||
| 548 | Riddle::Client::Filter.new attr.to_s, filter_value(val) | ||
| 549 | } if options[:with] | ||
| 550 | |||
| 551 | # exclusive attribute filters | ||
| 552 | client.filters += options[:without].collect { |attr,val| | ||
| 553 | Riddle::Client::Filter.new attr.to_s, filter_value(val), true | ||
| 554 | } if options[:without] | ||
| 555 | |||
| 556 | # every-match attribute filters | ||
| 557 | client.filters += options[:with_all].collect { |attr,vals| | ||
| 558 | Array(vals).collect { |val| | ||
| 559 | Riddle::Client::Filter.new attr.to_s, filter_value(val) | ||
| 560 | } | ||
| 561 | }.flatten if options[:with_all] | ||
| 562 | |||
| 563 | # exclusive attribute filter on primary key | ||
| 564 | client.filters += Array(options[:without_ids]).collect { |id| | ||
| 565 | Riddle::Client::Filter.new 'sphinx_internal_id', filter_value(id), true | ||
| 566 | } if options[:without_ids] | ||
| 567 | |||
| 568 | client | ||
| 569 | end | ||
| 570 | |||
| 571 | def star_query(query, custom_token = nil) | ||
| 572 | token = custom_token.is_a?(Regexp) ? custom_token : /\w+/u | ||
| 573 | |||
| 574 | query.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/u) do | ||
| 575 | pre, proper, post = $`, $&, $' | ||
| 576 | is_operator = pre.match(%r{(\W|^)[@~/]\Z}) # E.g. "@foo", "/2", "~3", but not as part of a token | ||
| 577 | is_quote = proper.starts_with?('"') && proper.ends_with?('"') # E.g. "foo bar", with quotes | ||
| 578 | has_star = pre.ends_with?("*") || post.starts_with?("*") | ||
| 579 | if is_operator || is_quote || has_star | ||
| 580 | proper | ||
| 581 | else | ||
| 582 | "*#{proper}*" | ||
| 583 | end | ||
| 584 | end | ||
| 585 | end | ||
| 586 | |||
| 587 | def filter_value(value) | ||
| 588 | case value | ||
| 589 | when Range | ||
| 590 | value.first.is_a?(Time) ? timestamp(value.first)..timestamp(value.last) : value | ||
| 591 | when Array | ||
| 592 | value.collect { |val| val.is_a?(Time) ? timestamp(val) : val } | ||
| 593 | else | ||
| 594 | Array(value) | ||
| 595 | end | ||
| 596 | end | ||
| 597 | |||
| 598 | # Returns the integer timestamp for a Time object. | ||
| 599 | # | ||
| 600 | # If using Rails 2.1+, need to handle timezones to translate them back to | ||
| 601 | # UTC, as that's what datetimes will be stored as by MySQL. | ||
| 602 | # | ||
| 603 | # in_time_zone is a method that was added for the timezone support in | ||
| 604 | # Rails 2.1, which is why it's used for testing. I'm sure there's better | ||
| 605 | # ways, but this does the job. | ||
| 606 | # | ||
| 607 | def timestamp(value) | ||
| 608 | value.respond_to?(:in_time_zone) ? value.utc.to_i : value.to_i | ||
| 609 | end | ||
| 610 | |||
| 611 | # Translate field and attribute conditions to the relevant search string | ||
| 612 | # and filters. | ||
| 613 | # | ||
| 614 | def search_conditions(klass, conditions={}) | ||
| 615 | attributes = klass ? klass.sphinx_indexes.collect { |index| | ||
| 616 | index.attributes.collect { |attrib| attrib.unique_name } | ||
| 617 | }.flatten : [] | ||
| 618 | |||
| 619 | search_string = [] | ||
| 620 | filters = [] | ||
| 621 | |||
| 622 | conditions.each do |key,val| | ||
| 623 | if attributes.include?(key.to_sym) | ||
| 624 | filters << Riddle::Client::Filter.new( | ||
| 625 | key.to_s, filter_value(val) | ||
| 626 | ) | ||
| 627 | else | ||
| 628 | search_string << "@#{key} #{val}" | ||
| 629 | end | ||
| 630 | end | ||
| 631 | |||
| 632 | return search_string.join(' '), filters | ||
| 633 | end | ||
| 634 | |||
| 635 | # Return the appropriate latitude and longitude values, depending on | ||
| 636 | # whether the relevant attributes have been defined, and also whether | ||
| 637 | # there's actually any values. | ||
| 638 | # | ||
| 639 | def anchor_conditions(klass, options) | ||
| 640 | attributes = klass ? klass.sphinx_indexes.collect { |index| | ||
| 641 | index.attributes.collect { |attrib| attrib.unique_name } | ||
| 642 | }.flatten : [] | ||
| 643 | |||
| 644 | lat_attr = klass ? klass.sphinx_indexes.collect { |index| | ||
| 645 | index.options[:latitude_attr] | ||
| 646 | }.compact.first : nil | ||
| 647 | |||
| 648 | lon_attr = klass ? klass.sphinx_indexes.collect { |index| | ||
| 649 | index.options[:longitude_attr] | ||
| 650 | }.compact.first : nil | ||
| 651 | |||
| 652 | lat_attr = options[:latitude_attr] if options[:latitude_attr] | ||
| 653 | lat_attr ||= :lat if attributes.include?(:lat) | ||
| 654 | lat_attr ||= :latitude if attributes.include?(:latitude) | ||
| 655 | |||
| 656 | lon_attr = options[:longitude_attr] if options[:longitude_attr] | ||
| 657 | lon_attr ||= :lng if attributes.include?(:lng) | ||
| 658 | lon_attr ||= :lon if attributes.include?(:lon) | ||
| 659 | lon_attr ||= :long if attributes.include?(:long) | ||
| 660 | lon_attr ||= :longitude if attributes.include?(:longitude) | ||
| 661 | |||
| 662 | lat = options[:lat] | ||
| 663 | lon = options[:lon] | ||
| 664 | |||
| 665 | if options[:geo] | ||
| 666 | lat = options[:geo].first | ||
| 667 | lon = options[:geo].last | ||
| 668 | end | ||
| 669 | |||
| 670 | lat && lon ? { | ||
| 671 | :latitude_attribute => lat_attr.to_s, | ||
| 672 | :latitude => lat, | ||
| 673 | :longitude_attribute => lon_attr.to_s, | ||
| 674 | :longitude => lon | ||
| 675 | } : nil | ||
| 676 | end | ||
| 677 | |||
| 678 | # Set the sort options using the :order key as well as the appropriate | ||
| 679 | # Riddle settings. | ||
| 680 | # | ||
| 681 | def set_sort_options!(client, options) | ||
| 682 | klass = options[:class] | ||
| 683 | fields = klass ? klass.sphinx_indexes.collect { |index| | ||
| 684 | index.fields.collect { |field| field.unique_name } | ||
| 685 | }.flatten : [] | ||
| 686 | index_options = klass ? klass.sphinx_index_options : {} | ||
| 687 | |||
| 688 | order = options[:order] || index_options[:order] | ||
| 689 | case order | ||
| 690 | when Symbol | ||
| 691 | client.sort_mode = :attr_asc if client.sort_mode == :relevance || client.sort_mode.nil? | ||
| 692 | if fields.include?(order) | ||
| 693 | client.sort_by = order.to_s.concat("_sort") | ||
| 694 | else | ||
| 695 | client.sort_by = order.to_s | ||
| 696 | end | ||
| 697 | when String | ||
| 698 | client.sort_mode = :extended | ||
| 699 | client.sort_by = sorted_fields_to_attributes(order, fields) | ||
| 700 | else | ||
| 701 | # do nothing | ||
| 702 | end | ||
| 703 | |||
| 704 | client.sort_mode = :attr_asc if client.sort_mode == :asc | ||
| 705 | client.sort_mode = :attr_desc if client.sort_mode == :desc | ||
| 706 | end | ||
| 707 | |||
| 708 | # Search through a collection of fields and translate any appearances | ||
| 709 | # of them in a string to their attribute equivalent for sorting. | ||
| 710 | # | ||
| 711 | def sorted_fields_to_attributes(string, fields) | ||
| 712 | fields.each { |field| | ||
| 713 | string.gsub!(/(^|\s)#{field}(,?\s|$)/) { |match| | ||
| 714 | match.gsub field.to_s, field.to_s.concat("_sort") | ||
| 715 | } | ||
| 716 | } | ||
| 717 | |||
| 718 | string | ||
| 719 | end | ||
| 720 | |||
| 721 | def facets_for_model(klass, args, options) | ||
| 722 | hash = ThinkingSphinx::FacetCollection.new args + [options] | ||
| 723 | options = options.clone.merge! :group_function => :attr | ||
| 724 | |||
| 725 | klass.sphinx_facets.inject(hash) do |hash, facet| | ||
| 726 | unless facet.name == :class && !options[:class_facet] | ||
| 727 | options[:group_by] = facet.attribute_name | ||
| 728 | hash.add_from_results facet, search(*(args + [options])) | ||
| 729 | end | ||
| 730 | |||
| 731 | hash | ||
| 732 | end | ||
| 733 | end | ||
| 734 | |||
| 735 | def facets_for_all_models(args, options) | ||
| 736 | options = GlobalFacetOptions.merge(options) | ||
| 737 | hash = ThinkingSphinx::FacetCollection.new args + [options] | ||
| 738 | options = options.merge! :group_function => :attr | ||
| 739 | |||
| 740 | facet_names(options).inject(hash) do |hash, name| | ||
| 741 | options[:group_by] = name | ||
| 742 | hash.add_from_results name, search(*(args + [options])) | ||
| 743 | hash | ||
| 744 | end | ||
| 745 | end | ||
| 746 | |||
| 747 | def facet_classes(options) | ||
| 748 | options[:classes] || ThinkingSphinx.indexed_models.collect { |model| | ||
| 749 | model.constantize | ||
| 750 | } | ||
| 751 | end | ||
| 752 | |||
| 753 | def facet_names(options) | ||
| 754 | classes = facet_classes(options) | ||
| 755 | names = options[:all_attributes] ? | ||
| 756 | facet_names_for_all_classes(classes) : | ||
| 757 | facet_names_common_to_all_classes(classes) | ||
| 758 | |||
| 759 | names.delete "class_crc" unless options[:class_facet] | ||
| 760 | names | ||
| 761 | end | ||
| 762 | |||
| 763 | def facet_names_for_all_classes(classes) | ||
| 764 | classes.collect { |klass| | ||
| 765 | klass.sphinx_facets.collect { |facet| facet.attribute_name } | ||
| 766 | }.flatten.uniq | ||
| 767 | end | ||
| 768 | |||
| 769 | def facet_names_common_to_all_classes(classes) | ||
| 770 | facet_names_for_all_classes(classes).select { |name| | ||
| 771 | classes.all? { |klass| | ||
| 772 | klass.sphinx_facets.detect { |facet| | ||
| 773 | facet.attribute_name == name | ||
| 774 | } | ||
| 775 | } | ||
| 776 | } | ||
| 777 | end | ||
| 778 | end | ||
| 779 | end | ||
| 780 | end | ||
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/tasks.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/tasks.rb deleted file mode 100644 index ab8990e..0000000 --- a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/tasks.rb +++ /dev/null | |||
| @@ -1,128 +0,0 @@ | |||
| 1 | require 'fileutils' | ||
| 2 | |||
| 3 | namespace :thinking_sphinx do | ||
| 4 | task :app_env do | ||
| 5 | Rake::Task[:environment].invoke if defined?(RAILS_ROOT) | ||
| 6 | Rake::Task[:merb_env].invoke if defined?(Merb) | ||
| 7 | end | ||
| 8 | |||
| 9 | desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings" | ||
| 10 | task :running_start => :app_env do | ||
| 11 | Rake::Task["thinking_sphinx:stop"].invoke if sphinx_running? | ||
| 12 | Rake::Task["thinking_sphinx:start"].invoke | ||
| 13 | end | ||
| 14 | |||
| 15 | desc "Start a Sphinx searchd daemon using Thinking Sphinx's settings" | ||
| 16 | task :start => :app_env do | ||
| 17 | config = ThinkingSphinx::Configuration.instance | ||
| 18 | |||
| 19 | FileUtils.mkdir_p config.searchd_file_path | ||
| 20 | raise RuntimeError, "searchd is already running." if sphinx_running? | ||
| 21 | |||
| 22 | Dir["#{config.searchd_file_path}/*.spl"].each { |file| File.delete(file) } | ||
| 23 | |||
| 24 | cmd = "#{config.bin_path}searchd --pidfile --config #{config.config_file}" | ||
| 25 | puts cmd | ||
| 26 | system cmd | ||
| 27 | |||
| 28 | sleep(2) | ||
| 29 | |||
| 30 | if sphinx_running? | ||
| 31 | puts "Started successfully (pid #{sphinx_pid})." | ||
| 32 | else | ||
| 33 | puts "Failed to start searchd daemon. Check #{config.searchd_log_file}." | ||
| 34 | end | ||
| 35 | end | ||
| 36 | |||
| 37 | desc "Stop Sphinx using Thinking Sphinx's settings" | ||
| 38 | task :stop => :app_env do | ||
| 39 | raise RuntimeError, "searchd is not running." unless sphinx_running? | ||
| 40 | config = ThinkingSphinx::Configuration.instance | ||
| 41 | pid = sphinx_pid | ||
| 42 | system "#{config.bin_path}searchd --stop --config #{config.config_file}" | ||
| 43 | puts "Stopped search daemon (pid #{pid})." | ||
| 44 | end | ||
| 45 | |||
| 46 | desc "Restart Sphinx" | ||
| 47 | task :restart => [:app_env, :stop, :start] | ||
| 48 | |||
| 49 | desc "Generate the Sphinx configuration file using Thinking Sphinx's settings" | ||
| 50 | task :configure => :app_env do | ||
| 51 | config = ThinkingSphinx::Configuration.instance | ||
| 52 | puts "Generating Configuration to #{config.config_file}" | ||
| 53 | config.build | ||
| 54 | end | ||
| 55 | |||
| 56 | desc "Index data for Sphinx using Thinking Sphinx's settings" | ||
| 57 | task :index => :app_env do | ||
| 58 | ThinkingSphinx::Deltas::Job.cancel_thinking_sphinx_jobs | ||
| 59 | |||
| 60 | config = ThinkingSphinx::Configuration.instance | ||
| 61 | unless ENV["INDEX_ONLY"] == "true" | ||
| 62 | puts "Generating Configuration to #{config.config_file}" | ||
| 63 | config.build | ||
| 64 | end | ||
| 65 | |||
| 66 | FileUtils.mkdir_p config.searchd_file_path | ||
| 67 | cmd = "#{config.bin_path}indexer --config #{config.config_file} --all" | ||
| 68 | cmd << " --rotate" if sphinx_running? | ||
| 69 | puts cmd | ||
| 70 | system cmd | ||
| 71 | end | ||
| 72 | |||
| 73 | namespace :index do | ||
| 74 | task :delta => :app_env do | ||
| 75 | ThinkingSphinx.indexed_models.select { |model| | ||
| 76 | model.constantize.sphinx_indexes.any? { |index| index.delta? } | ||
| 77 | }.each do |model| | ||
| 78 | model.constantize.sphinx_indexes.select { |index| | ||
| 79 | index.delta? && index.delta_object.respond_to?(:delayed_index) | ||
| 80 | }.each { |index| | ||
| 81 | index.delta_object.delayed_index(index.model) | ||
| 82 | } | ||
| 83 | end | ||
| 84 | end | ||
| 85 | end | ||
| 86 | |||
| 87 | desc "Process stored delta index requests" | ||
| 88 | task :delayed_delta => :app_env do | ||
| 89 | require 'delayed/worker' | ||
| 90 | |||
| 91 | Delayed::Worker.new( | ||
| 92 | :min_priority => ENV['MIN_PRIORITY'], | ||
| 93 | :max_priority => ENV['MAX_PRIORITY'] | ||
| 94 | ).start | ||
| 95 | end | ||
| 96 | end | ||
| 97 | |||
| 98 | namespace :ts do | ||
| 99 | desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings" | ||
| 100 | task :run => "thinking_sphinx:running_start" | ||
| 101 | desc "Start a Sphinx searchd daemon using Thinking Sphinx's settings" | ||
| 102 | task :start => "thinking_sphinx:start" | ||
| 103 | desc "Stop Sphinx using Thinking Sphinx's settings" | ||
| 104 | task :stop => "thinking_sphinx:stop" | ||
| 105 | desc "Index data for Sphinx using Thinking Sphinx's settings" | ||
| 106 | task :in => "thinking_sphinx:index" | ||
| 107 | namespace :in do | ||
| 108 | desc "Index Thinking Sphinx datetime delta indexes" | ||
| 109 | task :delta => "thinking_sphinx:index:delta" | ||
| 110 | end | ||
| 111 | task :index => "thinking_sphinx:index" | ||
| 112 | desc "Restart Sphinx" | ||
| 113 | task :restart => "thinking_sphinx:restart" | ||
| 114 | desc "Generate the Sphinx configuration file using Thinking Sphinx's settings" | ||
| 115 | task :conf => "thinking_sphinx:configure" | ||
| 116 | desc "Generate the Sphinx configuration file using Thinking Sphinx's settings" | ||
| 117 | task :config => "thinking_sphinx:configure" | ||
| 118 | desc "Process stored delta index requests" | ||
| 119 | task :dd => "thinking_sphinx:delayed_delta" | ||
| 120 | end | ||
| 121 | |||
| 122 | def sphinx_pid | ||
| 123 | ThinkingSphinx.sphinx_pid | ||
| 124 | end | ||
| 125 | |||
| 126 | def sphinx_running? | ||
| 127 | ThinkingSphinx.sphinx_running? | ||
| 128 | end | ||
