thinking-sphinx 3.1.4 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -2
  3. data/Appraisals +4 -4
  4. data/Gemfile +2 -0
  5. data/HISTORY +24 -0
  6. data/README.textile +5 -4
  7. data/gemfiles/rails_3_2.gemfile +2 -1
  8. data/gemfiles/rails_4_0.gemfile +2 -1
  9. data/gemfiles/rails_4_1.gemfile +2 -1
  10. data/gemfiles/rails_4_2.gemfile +2 -1
  11. data/lib/thinking_sphinx.rb +4 -1
  12. data/lib/thinking_sphinx/active_record.rb +1 -0
  13. data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
  14. data/lib/thinking_sphinx/active_record/attribute/type.rb +1 -1
  15. data/lib/thinking_sphinx/active_record/base.rb +1 -1
  16. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -1
  17. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +7 -3
  18. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +2 -2
  19. data/lib/thinking_sphinx/active_record/column_sql_presenter.rb +5 -1
  20. data/lib/thinking_sphinx/active_record/index.rb +4 -4
  21. data/lib/thinking_sphinx/active_record/source_joins.rb +55 -0
  22. data/lib/thinking_sphinx/active_record/sql_builder.rb +3 -18
  23. data/lib/thinking_sphinx/active_record/sql_builder/query.rb +7 -0
  24. data/lib/thinking_sphinx/active_record/sql_source.rb +7 -5
  25. data/lib/thinking_sphinx/active_record/sql_source/template.rb +1 -1
  26. data/lib/thinking_sphinx/callbacks.rb +18 -0
  27. data/lib/thinking_sphinx/configuration.rb +50 -33
  28. data/lib/thinking_sphinx/configuration/duplicate_names.rb +34 -0
  29. data/lib/thinking_sphinx/connection.rb +4 -4
  30. data/lib/thinking_sphinx/controller.rb +6 -4
  31. data/lib/thinking_sphinx/deletion.rb +13 -0
  32. data/lib/thinking_sphinx/errors.rb +8 -0
  33. data/lib/thinking_sphinx/index_set.rb +6 -1
  34. data/lib/thinking_sphinx/indexing_strategies/all_at_once.rb +7 -0
  35. data/lib/thinking_sphinx/indexing_strategies/one_at_a_time.rb +14 -0
  36. data/lib/thinking_sphinx/middlewares/active_record_translator.rb +1 -1
  37. data/lib/thinking_sphinx/middlewares/inquirer.rb +1 -1
  38. data/lib/thinking_sphinx/middlewares/sphinxql.rb +2 -2
  39. data/lib/thinking_sphinx/middlewares/stale_id_checker.rb +1 -1
  40. data/lib/thinking_sphinx/middlewares/stale_id_filter.rb +2 -2
  41. data/lib/thinking_sphinx/rake_interface.rb +14 -5
  42. data/lib/thinking_sphinx/real_time.rb +1 -0
  43. data/lib/thinking_sphinx/real_time/attribute.rb +7 -1
  44. data/lib/thinking_sphinx/real_time/index.rb +2 -0
  45. data/lib/thinking_sphinx/real_time/populator.rb +3 -3
  46. data/lib/thinking_sphinx/real_time/property.rb +1 -5
  47. data/lib/thinking_sphinx/real_time/transcriber.rb +44 -9
  48. data/lib/thinking_sphinx/real_time/translator.rb +36 -0
  49. data/lib/thinking_sphinx/search.rb +10 -0
  50. data/lib/thinking_sphinx/search/context.rb +8 -0
  51. data/lib/thinking_sphinx/search/stale_ids_exception.rb +3 -2
  52. data/lib/thinking_sphinx/subscribers/populator_subscriber.rb +1 -1
  53. data/lib/thinking_sphinx/tasks.rb +3 -1
  54. data/spec/acceptance/remove_deleted_records_spec.rb +18 -0
  55. data/spec/acceptance/searching_with_filters_spec.rb +13 -0
  56. data/spec/internal/app/indices/product_index.rb +1 -0
  57. data/spec/internal/db/schema.rb +1 -0
  58. data/spec/spec_helper.rb +1 -0
  59. data/spec/support/json_column.rb +29 -0
  60. data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +10 -0
  61. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +10 -0
  62. data/spec/thinking_sphinx/active_record/column_sql_presenter_spec.rb +37 -0
  63. data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +7 -17
  64. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +43 -15
  65. data/spec/thinking_sphinx/errors_spec.rb +7 -0
  66. data/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb +15 -1
  67. data/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb +1 -0
  68. data/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb +26 -6
  69. data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +4 -4
  70. data/spec/thinking_sphinx_spec.rb +2 -1
  71. data/thinking-sphinx.gemspec +1 -1
  72. metadata +12 -3
@@ -48,6 +48,6 @@ class ThinkingSphinx::ActiveRecord::SQLSource::Template
48
48
  end
49
49
 
50
50
  def primary_key
51
- source.model.primary_key.to_sym
51
+ source.options[:primary_key].to_sym
52
52
  end
53
53
  end
@@ -9,6 +9,24 @@ class ThinkingSphinx::Callbacks
9
9
  extend mod
10
10
  end
11
11
 
12
+ def self.resume!
13
+ @suspended = false
14
+ end
15
+
16
+ def self.suspend(&block)
17
+ suspend!
18
+ yield
19
+ resume!
20
+ end
21
+
22
+ def self.suspend!
23
+ @suspended = true
24
+ end
25
+
26
+ def self.suspended?
27
+ @suspended
28
+ end
29
+
12
30
  def initialize(instance)
13
31
  @instance = instance
14
32
  end
@@ -3,14 +3,16 @@ require 'pathname'
3
3
  class ThinkingSphinx::Configuration < Riddle::Configuration
4
4
  attr_accessor :configuration_file, :indices_location, :version
5
5
  attr_reader :index_paths
6
- attr_writer :controller, :index_set_class
6
+ attr_writer :controller, :index_set_class, :indexing_strategy
7
7
 
8
8
  delegate :environment, :to => :framework
9
9
 
10
+ @@mutex = Mutex.new
11
+
10
12
  def initialize
11
13
  super
12
14
 
13
- setup
15
+ reset
14
16
  end
15
17
 
16
18
  def self.instance
@@ -39,7 +41,7 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
39
41
 
40
42
  def framework=(framework)
41
43
  @framework = framework
42
- setup
44
+ reset
43
45
  framework
44
46
  end
45
47
 
@@ -60,6 +62,10 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
60
62
  @index_set_class ||= ThinkingSphinx::IndexSet
61
63
  end
62
64
 
65
+ def indexing_strategy
66
+ @indexing_strategy ||= ThinkingSphinx::IndexingStrategies::AllAtOnce
67
+ end
68
+
63
69
  def indices_for_references(*references)
64
70
  index_set_class.new(:references => references).to_a
65
71
  end
@@ -69,19 +75,21 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
69
75
  end
70
76
 
71
77
  def preload_indices
72
- return if @preloaded_indices
78
+ @@mutex.synchronize do
79
+ return if @preloaded_indices
73
80
 
74
- index_paths.each do |path|
75
- Dir["#{path}/**/*.rb"].sort.each do |file|
76
- ActiveSupport::Dependencies.require_or_load file
81
+ index_paths.each do |path|
82
+ Dir["#{path}/**/*.rb"].sort.each do |file|
83
+ ActiveSupport::Dependencies.require_or_load file
84
+ end
77
85
  end
78
- end
79
86
 
80
- if settings['distributed_indices'].nil? || settings['distributed_indices']
81
- ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
82
- end
87
+ if settings['distributed_indices'].nil? || settings['distributed_indices']
88
+ ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
89
+ end
83
90
 
84
- @preloaded_indices = true
91
+ @preloaded_indices = true
92
+ end
85
93
  end
86
94
 
87
95
  def render
@@ -89,6 +97,7 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
89
97
 
90
98
  ThinkingSphinx::Configuration::ConsistentIds.new(indices).reconcile
91
99
  ThinkingSphinx::Configuration::MinimumFields.new(indices).reconcile
100
+ ThinkingSphinx::Configuration::DuplicateNames.new(indices).reconcile
92
101
 
93
102
  super
94
103
  end
@@ -103,6 +112,28 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
103
112
  @settings ||= File.exists?(settings_file) ? settings_to_hash : {}
104
113
  end
105
114
 
115
+ def setup
116
+ @configuration_file = settings['configuration_file'] || framework_root.join(
117
+ 'config', "#{environment}.sphinx.conf"
118
+ ).to_s
119
+ @index_paths = engine_index_paths + [framework_root.join('app', 'indices').to_s]
120
+ @indices_location = settings['indices_location'] || framework_root.join(
121
+ 'db', 'sphinx', environment
122
+ ).to_s
123
+ @version = settings['version'] || '2.1.4'
124
+
125
+ if settings['common_sphinx_configuration']
126
+ common.common_sphinx_configuration = true
127
+ indexer.common_sphinx_configuration = true
128
+ end
129
+
130
+ configure_searchd
131
+
132
+ apply_sphinx_settings!
133
+
134
+ @offsets = {}
135
+ end
136
+
106
137
  private
107
138
 
108
139
  def configure_searchd
@@ -135,7 +166,10 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
135
166
  end
136
167
 
137
168
  def settings_to_hash
138
- contents = YAML.load(ERB.new(File.read(settings_file)).result)
169
+ input = File.read settings_file
170
+ input = ERB.new(input).result if defined?(ERB)
171
+
172
+ contents = YAML.load input
139
173
  contents && contents[environment] || {}
140
174
  end
141
175
 
@@ -143,27 +177,9 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
143
177
  framework_root.join 'config', 'thinking_sphinx.yml'
144
178
  end
145
179
 
146
- def setup
180
+ def reset
147
181
  @settings = nil
148
- @configuration_file = settings['configuration_file'] || framework_root.join(
149
- 'config', "#{environment}.sphinx.conf"
150
- ).to_s
151
- @index_paths = engine_index_paths + [framework_root.join('app', 'indices').to_s]
152
- @indices_location = settings['indices_location'] || framework_root.join(
153
- 'db', 'sphinx', environment
154
- ).to_s
155
- @version = settings['version'] || '2.1.4'
156
-
157
- if settings['common_sphinx_configuration']
158
- common.common_sphinx_configuration = true
159
- indexer.common_sphinx_configuration = true
160
- end
161
-
162
- configure_searchd
163
-
164
- apply_sphinx_settings!
165
-
166
- @offsets = {}
182
+ setup
167
183
  end
168
184
 
169
185
  def tmp_path
@@ -190,4 +206,5 @@ end
190
206
  require 'thinking_sphinx/configuration/consistent_ids'
191
207
  require 'thinking_sphinx/configuration/defaults'
192
208
  require 'thinking_sphinx/configuration/distributed_indices'
209
+ require 'thinking_sphinx/configuration/duplicate_names'
193
210
  require 'thinking_sphinx/configuration/minimum_fields'
@@ -0,0 +1,34 @@
1
+ class ThinkingSphinx::Configuration::DuplicateNames
2
+ def initialize(indices)
3
+ @indices = indices
4
+ end
5
+
6
+ def reconcile
7
+ indices.each do |index|
8
+ return if index.distributed?
9
+
10
+ counts_for(index).each do |name, count|
11
+ next if count <= 1
12
+
13
+ raise ThinkingSphinx::DuplicateNameError,
14
+ "Duplicate field/attribute name '#{name}' in index '#{index.name}'"
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :indices
22
+
23
+ def counts_for(index)
24
+ names_for(index).inject({}) do |hash, name|
25
+ hash[name] ||= 0
26
+ hash[name] += 1
27
+ hash
28
+ end
29
+ end
30
+
31
+ def names_for(index)
32
+ index.fields.collect(&:name) + index.attributes.collect(&:name)
33
+ end
34
+ end
@@ -157,13 +157,13 @@ module ThinkingSphinx::Connection
157
157
  def set_to_array(set)
158
158
  return nil if set.nil?
159
159
 
160
- meta = set.meta_data
160
+ meta = set.getMetaData
161
161
  rows = []
162
162
 
163
163
  while set.next
164
- rows << (1..meta.column_count).inject({}) do |row, index|
165
- name = meta.column_name index
166
- row[name] = set.get_object(index)
164
+ rows << (1..meta.getColumnCount).inject({}) do |row, index|
165
+ name = meta.getColumnName index
166
+ row[name] = set.getObject(index)
167
167
  row
168
168
  end
169
169
  end
@@ -1,10 +1,12 @@
1
1
  class ThinkingSphinx::Controller < Riddle::Controller
2
2
  def index(*indices)
3
- options = indices.extract_options!
4
- indices << '--all' if indices.empty?
3
+ configuration = ThinkingSphinx::Configuration.instance
4
+ options = indices.extract_options!
5
5
 
6
- ThinkingSphinx::Guard::Files.call(indices) do |names|
7
- super(*(names + [options]))
6
+ configuration.indexing_strategy.call(indices) do |index_names|
7
+ ThinkingSphinx::Guard::Files.call(index_names) do |names|
8
+ super(*(names + [options]))
9
+ end
8
10
  end
9
11
  end
10
12
  end
@@ -32,8 +32,21 @@ class ThinkingSphinx::Deletion
32
32
 
33
33
  class RealtimeDeletion < ThinkingSphinx::Deletion
34
34
  def perform
35
+ return unless callbacks_enabled?
36
+
35
37
  execute Riddle::Query::Delete.new(name, document_ids_for_keys).to_sql
36
38
  end
39
+
40
+ private
41
+
42
+ def callbacks_enabled?
43
+ setting = configuration.settings['real_time_callbacks']
44
+ setting.nil? || setting
45
+ end
46
+
47
+ def configuration
48
+ ThinkingSphinx::Configuration.instance
49
+ end
37
50
  end
38
51
 
39
52
  class PlainDeletion < ThinkingSphinx::Deletion
@@ -13,6 +13,8 @@ class ThinkingSphinx::SphinxError < StandardError
13
13
  replacement = ThinkingSphinx::ConnectionError.new(
14
14
  "Error connecting to Sphinx via the MySQL protocol. #{error.message}"
15
15
  )
16
+ when /offset out of bounds/
17
+ replacement = ThinkingSphinx::OutOfBoundsError.new(error.message)
16
18
  else
17
19
  replacement = new(error.message)
18
20
  end
@@ -35,6 +37,9 @@ end
35
37
  class ThinkingSphinx::ParseError < ThinkingSphinx::QueryError
36
38
  end
37
39
 
40
+ class ThinkingSphinx::OutOfBoundsError < ThinkingSphinx::QueryError
41
+ end
42
+
38
43
  class ThinkingSphinx::QueryExecutionError < StandardError
39
44
  attr_accessor :statement
40
45
  end
@@ -50,3 +55,6 @@ end
50
55
 
51
56
  class ThinkingSphinx::PopulatedResultsError < StandardError
52
57
  end
58
+
59
+ class ThinkingSphinx::DuplicateNameError < StandardError
60
+ end
@@ -1,5 +1,10 @@
1
1
  class ThinkingSphinx::IndexSet
2
2
  include Enumerable
3
+
4
+ def self.reference_name(klass)
5
+ @cached_results ||= {}
6
+ @cached_results[klass.name] ||= klass.name.underscore.to_sym
7
+ end
3
8
 
4
9
  delegate :each, :empty?, :to => :indices
5
10
 
@@ -63,7 +68,7 @@ class ThinkingSphinx::IndexSet
63
68
 
64
69
  def references
65
70
  options[:references] || classes_and_ancestors.collect { |klass|
66
- klass.name.underscore.to_sym
71
+ ThinkingSphinx::IndexSet.reference_name(klass)
67
72
  }
68
73
  end
69
74
 
@@ -0,0 +1,7 @@
1
+ class ThinkingSphinx::IndexingStrategies::AllAtOnce
2
+ def self.call(indices = [], &block)
3
+ indices << '--all' if indices.empty?
4
+
5
+ block.call indices
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ class ThinkingSphinx::IndexingStrategies::OneAtATime
2
+ def self.call(indices = [], &block)
3
+ if indices.empty?
4
+ configuration = ThinkingSphinx::Configuration.instance
5
+ configuration.preload_indices
6
+
7
+ indices = configuration.indices.select { |index|
8
+ !(index.distributed? || index.type == 'rt')
9
+ }.collect &:name
10
+ end
11
+
12
+ indices.each { |name| block.call [name] }
13
+ end
14
+ end
@@ -57,7 +57,7 @@ class ThinkingSphinx::Middlewares::ActiveRecordTranslator <
57
57
  @results_for_models ||= model_names.inject({}) do |hash, name|
58
58
  model = name.constantize
59
59
  hash[name] = model_relation_with_sql_options(model.unscoped).where(
60
- model.primary_key => ids_for_model(name)
60
+ (context.configuration.settings[:primary_key] || model.primary_key || :id) => ids_for_model(name)
61
61
  )
62
62
 
63
63
  hash
@@ -45,7 +45,7 @@ class ThinkingSphinx::Middlewares::Inquirer <
45
45
 
46
46
  def call(raw_results, meta_results)
47
47
  context[:results] = raw_results.to_a
48
- context[:raw] = raw_results
48
+ context[:raw] = context[:results].dup
49
49
  context[:meta] = meta_results.inject({}) { |hash, row|
50
50
  hash[row['Variable_name']] = row['Value']
51
51
  hash
@@ -4,7 +4,7 @@ class ThinkingSphinx::Middlewares::SphinxQL <
4
4
  SELECT_OPTIONS = [:agent_query_timeout, :boolean_simplify, :comment, :cutoff,
5
5
  :field_weights, :global_idf, :idf, :index_weights, :max_matches,
6
6
  :max_query_time, :max_predicted_time, :ranker, :retry_count, :retry_delay,
7
- :reverse_scan, :sort_method]
7
+ :reverse_scan, :sort_method, :rand_seed]
8
8
 
9
9
  def call(contexts)
10
10
  contexts.each do |context|
@@ -82,7 +82,7 @@ class ThinkingSphinx::Middlewares::SphinxQL <
82
82
 
83
83
  def indices_match_classes?
84
84
  indices.collect(&:reference).uniq.sort == classes.collect { |klass|
85
- klass.name.underscore.to_sym
85
+ ThinkingSphinx::IndexSet.reference_name(klass)
86
86
  }.sort
87
87
  end
88
88
 
@@ -33,7 +33,7 @@ class ThinkingSphinx::Middlewares::StaleIdChecker <
33
33
  end
34
34
 
35
35
  def raise_exception
36
- raise ThinkingSphinx::Search::StaleIdsException, stale_ids
36
+ raise ThinkingSphinx::Search::StaleIdsException.new(stale_ids, context)
37
37
  end
38
38
 
39
39
  def stale_ids
@@ -11,7 +11,7 @@ class ThinkingSphinx::Middlewares::StaleIdFilter <
11
11
  rescue ThinkingSphinx::Search::StaleIdsException => error
12
12
  raise error if @retries <= 0
13
13
 
14
- append_stale_ids error.ids
14
+ append_stale_ids error.ids, error.context
15
15
  ThinkingSphinx::Logger.log :message, log_message
16
16
 
17
17
  @retries -= 1 and retry
@@ -20,7 +20,7 @@ class ThinkingSphinx::Middlewares::StaleIdFilter <
20
20
 
21
21
  private
22
22
 
23
- def append_stale_ids(ids)
23
+ def append_stale_ids(ids, context)
24
24
  @stale_ids |= ids
25
25
 
26
26
  context.search.options[:without_ids] ||= []
@@ -45,16 +45,25 @@ class ThinkingSphinx::RakeInterface
45
45
  FileUtils.mkdir_p configuration.indices_location
46
46
  end
47
47
 
48
- def start
48
+ def start(options={})
49
49
  raise RuntimeError, 'searchd is already running' if controller.running?
50
50
 
51
51
  FileUtils.mkdir_p configuration.indices_location
52
- controller.start
53
52
 
54
- if controller.running?
55
- puts "Started searchd successfully (pid: #{controller.pid})."
53
+ if options[:nodetach]
54
+ unless pid = fork
55
+ controller.start(options)
56
+ end
57
+ Signal.trap('TERM') { Process.kill(:TERM, pid); }
58
+ Signal.trap('INT') { Process.kill(:TERM, pid); }
59
+ Process.wait(pid)
56
60
  else
57
- puts "Failed to start searchd. Check the log files for more information."
61
+ controller.start(options)
62
+ if controller.running?
63
+ puts "Started searchd successfully (pid: #{controller.pid})."
64
+ else
65
+ puts "Failed to start searchd. Check the log files for more information."
66
+ end
58
67
  end
59
68
  end
60
69