thinking-sphinx 3.1.4 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +7 -2
- data/Appraisals +4 -4
- data/Gemfile +2 -0
- data/HISTORY +24 -0
- data/README.textile +5 -4
- data/gemfiles/rails_3_2.gemfile +2 -1
- data/gemfiles/rails_4_0.gemfile +2 -1
- data/gemfiles/rails_4_1.gemfile +2 -1
- data/gemfiles/rails_4_2.gemfile +2 -1
- data/lib/thinking_sphinx.rb +4 -1
- data/lib/thinking_sphinx/active_record.rb +1 -0
- data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
- data/lib/thinking_sphinx/active_record/attribute/type.rb +1 -1
- data/lib/thinking_sphinx/active_record/base.rb +1 -1
- data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -1
- data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +7 -3
- data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +2 -2
- data/lib/thinking_sphinx/active_record/column_sql_presenter.rb +5 -1
- data/lib/thinking_sphinx/active_record/index.rb +4 -4
- data/lib/thinking_sphinx/active_record/source_joins.rb +55 -0
- data/lib/thinking_sphinx/active_record/sql_builder.rb +3 -18
- data/lib/thinking_sphinx/active_record/sql_builder/query.rb +7 -0
- data/lib/thinking_sphinx/active_record/sql_source.rb +7 -5
- data/lib/thinking_sphinx/active_record/sql_source/template.rb +1 -1
- data/lib/thinking_sphinx/callbacks.rb +18 -0
- data/lib/thinking_sphinx/configuration.rb +50 -33
- data/lib/thinking_sphinx/configuration/duplicate_names.rb +34 -0
- data/lib/thinking_sphinx/connection.rb +4 -4
- data/lib/thinking_sphinx/controller.rb +6 -4
- data/lib/thinking_sphinx/deletion.rb +13 -0
- data/lib/thinking_sphinx/errors.rb +8 -0
- data/lib/thinking_sphinx/index_set.rb +6 -1
- data/lib/thinking_sphinx/indexing_strategies/all_at_once.rb +7 -0
- data/lib/thinking_sphinx/indexing_strategies/one_at_a_time.rb +14 -0
- data/lib/thinking_sphinx/middlewares/active_record_translator.rb +1 -1
- data/lib/thinking_sphinx/middlewares/inquirer.rb +1 -1
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +2 -2
- data/lib/thinking_sphinx/middlewares/stale_id_checker.rb +1 -1
- data/lib/thinking_sphinx/middlewares/stale_id_filter.rb +2 -2
- data/lib/thinking_sphinx/rake_interface.rb +14 -5
- data/lib/thinking_sphinx/real_time.rb +1 -0
- data/lib/thinking_sphinx/real_time/attribute.rb +7 -1
- data/lib/thinking_sphinx/real_time/index.rb +2 -0
- data/lib/thinking_sphinx/real_time/populator.rb +3 -3
- data/lib/thinking_sphinx/real_time/property.rb +1 -5
- data/lib/thinking_sphinx/real_time/transcriber.rb +44 -9
- data/lib/thinking_sphinx/real_time/translator.rb +36 -0
- data/lib/thinking_sphinx/search.rb +10 -0
- data/lib/thinking_sphinx/search/context.rb +8 -0
- data/lib/thinking_sphinx/search/stale_ids_exception.rb +3 -2
- data/lib/thinking_sphinx/subscribers/populator_subscriber.rb +1 -1
- data/lib/thinking_sphinx/tasks.rb +3 -1
- data/spec/acceptance/remove_deleted_records_spec.rb +18 -0
- data/spec/acceptance/searching_with_filters_spec.rb +13 -0
- data/spec/internal/app/indices/product_index.rb +1 -0
- data/spec/internal/db/schema.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/json_column.rb +29 -0
- data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +10 -0
- data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +10 -0
- data/spec/thinking_sphinx/active_record/column_sql_presenter_spec.rb +37 -0
- data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +7 -17
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +43 -15
- data/spec/thinking_sphinx/errors_spec.rb +7 -0
- data/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb +15 -1
- data/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb +1 -0
- data/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb +26 -6
- data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +4 -4
- data/spec/thinking_sphinx_spec.rb +2 -1
- data/thinking-sphinx.gemspec +1 -1
- metadata +12 -3
@@ -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
|
-
|
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
|
-
|
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
|
-
|
78
|
+
@@mutex.synchronize do
|
79
|
+
return if @preloaded_indices
|
73
80
|
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
87
|
+
if settings['distributed_indices'].nil? || settings['distributed_indices']
|
88
|
+
ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
|
89
|
+
end
|
83
90
|
|
84
|
-
|
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
|
-
|
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
|
180
|
+
def reset
|
147
181
|
@settings = nil
|
148
|
-
|
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.
|
160
|
+
meta = set.getMetaData
|
161
161
|
rows = []
|
162
162
|
|
163
163
|
while set.next
|
164
|
-
rows << (1..meta.
|
165
|
-
name = meta.
|
166
|
-
row[name] = set.
|
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
|
-
|
4
|
-
|
3
|
+
configuration = ThinkingSphinx::Configuration.instance
|
4
|
+
options = indices.extract_options!
|
5
5
|
|
6
|
-
|
7
|
-
|
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
|
71
|
+
ThinkingSphinx::IndexSet.reference_name(klass)
|
67
72
|
}
|
68
73
|
end
|
69
74
|
|
@@ -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] =
|
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
|
85
|
+
ThinkingSphinx::IndexSet.reference_name(klass)
|
86
86
|
}.sort
|
87
87
|
end
|
88
88
|
|
@@ -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
|
55
|
-
|
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
|
-
|
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
|
|