thinking-sphinx 3.3.0 → 3.4.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/.gitignore +1 -0
- data/.travis.yml +29 -20
- data/Appraisals +9 -5
- data/Gemfile +8 -3
- data/HISTORY +24 -0
- data/README.textile +5 -4
- data/bin/console +14 -0
- data/bin/literals +9 -0
- data/bin/loadsphinx +38 -0
- data/lib/thinking_sphinx.rb +15 -2
- data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +2 -3
- data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +11 -1
- data/lib/thinking_sphinx/active_record/index.rb +1 -1
- data/lib/thinking_sphinx/active_record/join_association.rb +3 -1
- data/lib/thinking_sphinx/active_record/log_subscriber.rb +5 -0
- data/lib/thinking_sphinx/active_record/sql_source.rb +1 -1
- data/lib/thinking_sphinx/attribute_types.rb +70 -0
- data/lib/thinking_sphinx/commands/base.rb +41 -0
- data/lib/thinking_sphinx/commands/configure.rb +13 -0
- data/lib/thinking_sphinx/commands/index.rb +11 -0
- data/lib/thinking_sphinx/commands/start_attached.rb +20 -0
- data/lib/thinking_sphinx/commands/start_detached.rb +19 -0
- data/lib/thinking_sphinx/commands/stop.rb +22 -0
- data/lib/thinking_sphinx/configuration.rb +36 -28
- data/lib/thinking_sphinx/configuration/minimum_fields.rb +11 -8
- data/lib/thinking_sphinx/connection.rb +5 -122
- data/lib/thinking_sphinx/connection/client.rb +48 -0
- data/lib/thinking_sphinx/connection/jruby.rb +53 -0
- data/lib/thinking_sphinx/connection/mri.rb +28 -0
- data/lib/thinking_sphinx/core/index.rb +11 -0
- data/lib/thinking_sphinx/deletion.rb +6 -2
- data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
- data/lib/thinking_sphinx/deltas/delete_job.rb +14 -4
- data/lib/thinking_sphinx/distributed/index.rb +10 -0
- data/lib/thinking_sphinx/errors.rb +1 -1
- data/lib/thinking_sphinx/index_set.rb +14 -2
- data/lib/thinking_sphinx/interfaces/daemon.rb +32 -0
- data/lib/thinking_sphinx/interfaces/real_time.rb +41 -0
- data/lib/thinking_sphinx/interfaces/sql.rb +41 -0
- data/lib/thinking_sphinx/middlewares.rb +5 -3
- data/lib/thinking_sphinx/middlewares/active_record_translator.rb +13 -6
- data/lib/thinking_sphinx/middlewares/attribute_typer.rb +48 -0
- data/lib/thinking_sphinx/middlewares/valid_options.rb +23 -0
- data/lib/thinking_sphinx/rake_interface.rb +10 -124
- data/lib/thinking_sphinx/search.rb +11 -0
- data/lib/thinking_sphinx/search/query.rb +7 -1
- data/lib/thinking_sphinx/tasks.rb +80 -21
- data/lib/thinking_sphinx/with_output.rb +11 -0
- data/spec/acceptance/connection_spec.rb +4 -4
- data/spec/acceptance/searching_within_a_model_spec.rb +7 -0
- data/spec/acceptance/specifying_sql_spec.rb +26 -8
- data/spec/acceptance/sql_deltas_spec.rb +12 -0
- data/spec/internal/app/indices/album_index.rb +3 -0
- data/spec/internal/app/models/album.rb +19 -0
- data/spec/internal/db/schema.rb +8 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/json_column.rb +5 -1
- data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +5 -1
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +6 -0
- data/spec/thinking_sphinx/attribute_types_spec.rb +50 -0
- data/spec/thinking_sphinx/commands/configure_spec.rb +29 -0
- data/spec/thinking_sphinx/commands/index_spec.rb +26 -0
- data/spec/thinking_sphinx/commands/start_detached_spec.rb +55 -0
- data/spec/thinking_sphinx/commands/stop_spec.rb +54 -0
- data/spec/thinking_sphinx/configuration/minimum_fields_spec.rb +36 -0
- data/spec/thinking_sphinx/deletion_spec.rb +2 -5
- data/spec/thinking_sphinx/deltas/default_delta_spec.rb +1 -1
- data/spec/thinking_sphinx/errors_spec.rb +7 -0
- data/spec/thinking_sphinx/index_set_spec.rb +30 -7
- data/spec/thinking_sphinx/interfaces/daemon_spec.rb +52 -0
- data/spec/thinking_sphinx/interfaces/real_time_spec.rb +109 -0
- data/spec/thinking_sphinx/interfaces/sql_spec.rb +98 -0
- data/spec/thinking_sphinx/middlewares/attribute_typer_spec.rb +42 -0
- data/spec/thinking_sphinx/middlewares/valid_options_spec.rb +49 -0
- data/spec/thinking_sphinx/rake_interface_spec.rb +13 -246
- data/spec/thinking_sphinx/search/query_spec.rb +7 -0
- data/thinking-sphinx.gemspec +5 -4
- metadata +72 -16
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/rails_3_2.gemfile +0 -13
- data/gemfiles/rails_4_0.gemfile +0 -13
- data/gemfiles/rails_4_1.gemfile +0 -13
- data/gemfiles/rails_4_2.gemfile +0 -13
- data/gemfiles/rails_5_0.gemfile +0 -12
@@ -0,0 +1,41 @@
|
|
1
|
+
class ThinkingSphinx::Commands::Base
|
2
|
+
include ThinkingSphinx::WithOutput
|
3
|
+
|
4
|
+
def self.call(configuration, options, stream = STDOUT)
|
5
|
+
new(configuration, options, stream).call_with_handling
|
6
|
+
end
|
7
|
+
|
8
|
+
def call_with_handling
|
9
|
+
call
|
10
|
+
rescue Riddle::CommandFailedError => error
|
11
|
+
handle_failure error.command_result
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
delegate :controller, :to => :configuration
|
17
|
+
|
18
|
+
def command_output(output)
|
19
|
+
return "See above\n" if output.nil?
|
20
|
+
|
21
|
+
"\n\t" + output.gsub("\n", "\n\t")
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle_failure(result)
|
25
|
+
stream.puts <<-TXT
|
26
|
+
|
27
|
+
The Sphinx #{type} command failed:
|
28
|
+
Command: #{result.command}
|
29
|
+
Status: #{result.status}
|
30
|
+
Output: #{command_output result.output}
|
31
|
+
There may be more information about the failure in #{configuration.searchd.log}.
|
32
|
+
TXT
|
33
|
+
exit result.status
|
34
|
+
end
|
35
|
+
|
36
|
+
def log(message)
|
37
|
+
return if options[:silent]
|
38
|
+
|
39
|
+
stream.puts message
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class ThinkingSphinx::Commands::StartAttached < ThinkingSphinx::Commands::Base
|
2
|
+
def call
|
3
|
+
FileUtils.mkdir_p configuration.indices_location
|
4
|
+
|
5
|
+
unless pid = fork
|
6
|
+
controller.start :verbose => options[:verbose], :nodetach => true
|
7
|
+
end
|
8
|
+
|
9
|
+
Signal.trap('TERM') { Process.kill(:TERM, pid) }
|
10
|
+
Signal.trap('INT') { Process.kill(:TERM, pid) }
|
11
|
+
|
12
|
+
Process.wait(pid)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def type
|
18
|
+
'start'
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class ThinkingSphinx::Commands::StartDetached < ThinkingSphinx::Commands::Base
|
2
|
+
def call
|
3
|
+
FileUtils.mkdir_p configuration.indices_location
|
4
|
+
|
5
|
+
result = controller.start :verbose => options[:verbose]
|
6
|
+
|
7
|
+
if controller.running?
|
8
|
+
log "Started searchd successfully (pid: #{controller.pid})."
|
9
|
+
else
|
10
|
+
handle_failure result
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def type
|
17
|
+
'start'
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class ThinkingSphinx::Commands::Stop < ThinkingSphinx::Commands::Base
|
2
|
+
def call
|
3
|
+
unless controller.running?
|
4
|
+
log 'searchd is not currently running.'
|
5
|
+
return
|
6
|
+
end
|
7
|
+
|
8
|
+
pid = controller.pid
|
9
|
+
until !controller.running? do
|
10
|
+
controller.stop options
|
11
|
+
sleep(0.5)
|
12
|
+
end
|
13
|
+
|
14
|
+
log "Stopped searchd daemon (pid: #{pid})."
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def type
|
20
|
+
'stop'
|
21
|
+
end
|
22
|
+
end
|
@@ -84,9 +84,8 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
end
|
87
|
+
normalise
|
88
|
+
verify
|
90
89
|
|
91
90
|
@preloaded_indices = true
|
92
91
|
end
|
@@ -95,10 +94,6 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
95
94
|
def render
|
96
95
|
preload_indices
|
97
96
|
|
98
|
-
ThinkingSphinx::Configuration::ConsistentIds.new(indices).reconcile
|
99
|
-
ThinkingSphinx::Configuration::MinimumFields.new(indices).reconcile
|
100
|
-
ThinkingSphinx::Configuration::DuplicateNames.new(indices).reconcile
|
101
|
-
|
102
97
|
super
|
103
98
|
end
|
104
99
|
|
@@ -137,6 +132,16 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
137
132
|
|
138
133
|
private
|
139
134
|
|
135
|
+
def apply_sphinx_settings!
|
136
|
+
sphinx_sections.each do |object|
|
137
|
+
settings.each do |key, value|
|
138
|
+
next unless object.class.settings.include?(key.to_sym)
|
139
|
+
|
140
|
+
object.send("#{key}=", value)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
140
145
|
def configure_searchd
|
141
146
|
configure_searchd_log_files
|
142
147
|
|
@@ -153,12 +158,21 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
153
158
|
searchd.query_log = log_root.join("#{environment}.searchd.query.log").to_s
|
154
159
|
end
|
155
160
|
|
161
|
+
def framework_root
|
162
|
+
Pathname.new(framework.root)
|
163
|
+
end
|
164
|
+
|
156
165
|
def log_root
|
157
166
|
real_path 'log'
|
158
167
|
end
|
159
168
|
|
160
|
-
def
|
161
|
-
|
169
|
+
def normalise
|
170
|
+
if settings['distributed_indices'].nil? || settings['distributed_indices']
|
171
|
+
ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
|
172
|
+
end
|
173
|
+
|
174
|
+
ThinkingSphinx::Configuration::ConsistentIds.new(indices).reconcile
|
175
|
+
ThinkingSphinx::Configuration::MinimumFields.new(indices).reconcile
|
162
176
|
end
|
163
177
|
|
164
178
|
def real_path(*arguments)
|
@@ -166,25 +180,21 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
166
180
|
path.exist? ? path.realpath : path
|
167
181
|
end
|
168
182
|
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
contents = YAML.load input
|
174
|
-
contents && contents[environment] || {}
|
183
|
+
def reset
|
184
|
+
@settings = nil
|
185
|
+
setup
|
175
186
|
end
|
176
187
|
|
177
188
|
def settings_file
|
178
189
|
framework_root.join 'config', 'thinking_sphinx.yml'
|
179
190
|
end
|
180
191
|
|
181
|
-
def
|
182
|
-
|
183
|
-
|
184
|
-
end
|
192
|
+
def settings_to_hash
|
193
|
+
input = File.read settings_file
|
194
|
+
input = ERB.new(input).result if defined?(ERB)
|
185
195
|
|
186
|
-
|
187
|
-
|
196
|
+
contents = YAML.load input
|
197
|
+
contents && contents[environment] || {}
|
188
198
|
end
|
189
199
|
|
190
200
|
def sphinx_sections
|
@@ -193,14 +203,12 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
193
203
|
sections
|
194
204
|
end
|
195
205
|
|
196
|
-
def
|
197
|
-
|
198
|
-
|
199
|
-
next unless object.class.settings.include?(key.to_sym)
|
206
|
+
def tmp_path
|
207
|
+
real_path 'tmp'
|
208
|
+
end
|
200
209
|
|
201
|
-
|
202
|
-
|
203
|
-
end
|
210
|
+
def verify
|
211
|
+
ThinkingSphinx::Configuration::DuplicateNames.new(indices).reconcile
|
204
212
|
end
|
205
213
|
end
|
206
214
|
|
@@ -6,8 +6,8 @@ class ThinkingSphinx::Configuration::MinimumFields
|
|
6
6
|
def reconcile
|
7
7
|
return unless no_inheritance_columns?
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
field_collections.each do |collection|
|
10
|
+
collection.fields.delete_if do |field|
|
11
11
|
field.name == 'sphinx_internal_class_name'
|
12
12
|
end
|
13
13
|
end
|
@@ -17,15 +17,18 @@ class ThinkingSphinx::Configuration::MinimumFields
|
|
17
17
|
|
18
18
|
attr_reader :indices
|
19
19
|
|
20
|
+
def field_collections
|
21
|
+
indices_of_type('plain').collect(&:sources).flatten +
|
22
|
+
indices_of_type('rt')
|
23
|
+
end
|
24
|
+
|
25
|
+
def indices_of_type(type)
|
26
|
+
indices.select { |index| index.type == type }
|
27
|
+
end
|
28
|
+
|
20
29
|
def no_inheritance_columns?
|
21
30
|
indices.select { |index|
|
22
31
|
index.model.column_names.include?(index.model.inheritance_column)
|
23
32
|
}.empty?
|
24
33
|
end
|
25
|
-
|
26
|
-
def sources
|
27
|
-
@sources ||= @indices.select { |index|
|
28
|
-
index.respond_to?(:sources)
|
29
|
-
}.collect(&:sources).flatten
|
30
|
-
end
|
31
34
|
end
|
@@ -26,7 +26,7 @@ module ThinkingSphinx::Connection
|
|
26
26
|
def self.pool
|
27
27
|
@pool ||= Innertube::Pool.new(
|
28
28
|
Proc.new { ThinkingSphinx::Connection.new },
|
29
|
-
Proc.new { |connection| connection.close }
|
29
|
+
Proc.new { |connection| connection.close! }
|
30
30
|
)
|
31
31
|
end
|
32
32
|
|
@@ -63,125 +63,8 @@ module ThinkingSphinx::Connection
|
|
63
63
|
end
|
64
64
|
|
65
65
|
@persistent = true
|
66
|
-
|
67
|
-
class Client
|
68
|
-
def close
|
69
|
-
client.close unless ThinkingSphinx::Connection.persistent?
|
70
|
-
end
|
71
|
-
|
72
|
-
def execute(statement)
|
73
|
-
check_and_perform(statement).first
|
74
|
-
end
|
75
|
-
|
76
|
-
def query_all(*statements)
|
77
|
-
check_and_perform statements.join('; ')
|
78
|
-
end
|
79
|
-
|
80
|
-
private
|
81
|
-
|
82
|
-
def check(statements)
|
83
|
-
if statements.length > ThinkingSphinx::MAXIMUM_STATEMENT_LENGTH
|
84
|
-
exception = ThinkingSphinx::QueryLengthError.new
|
85
|
-
exception.statement = statements
|
86
|
-
raise exception
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def check_and_perform(statements)
|
91
|
-
check statements
|
92
|
-
perform statements
|
93
|
-
end
|
94
|
-
|
95
|
-
def close_and_clear
|
96
|
-
client.close
|
97
|
-
@client = nil
|
98
|
-
end
|
99
|
-
|
100
|
-
def perform(statements)
|
101
|
-
results_for statements
|
102
|
-
rescue => error
|
103
|
-
message = "#{error.message} - #{statements}"
|
104
|
-
wrapper = ThinkingSphinx::QueryExecutionError.new message
|
105
|
-
wrapper.statement = statements
|
106
|
-
raise wrapper
|
107
|
-
ensure
|
108
|
-
close_and_clear unless ThinkingSphinx::Connection.persistent?
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
class MRI < Client
|
113
|
-
def initialize(options)
|
114
|
-
@options = options
|
115
|
-
end
|
116
|
-
|
117
|
-
def base_error
|
118
|
-
Mysql2::Error
|
119
|
-
end
|
120
|
-
|
121
|
-
private
|
122
|
-
|
123
|
-
attr_reader :options
|
124
|
-
|
125
|
-
def client
|
126
|
-
@client ||= Mysql2::Client.new({
|
127
|
-
:flags => Mysql2::Client::MULTI_STATEMENTS
|
128
|
-
}.merge(options))
|
129
|
-
rescue base_error => error
|
130
|
-
raise ThinkingSphinx::SphinxError.new_from_mysql error
|
131
|
-
end
|
132
|
-
|
133
|
-
def results_for(statements)
|
134
|
-
results = [client.query(statements)]
|
135
|
-
results << client.store_result while client.next_result
|
136
|
-
results
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
class JRuby < Client
|
141
|
-
attr_reader :address, :options
|
142
|
-
|
143
|
-
def initialize(options)
|
144
|
-
@address = "jdbc:mysql://#{options[:host]}:#{options[:port]}/?allowMultiQueries=true"
|
145
|
-
@options = options
|
146
|
-
end
|
147
|
-
|
148
|
-
def base_error
|
149
|
-
Java::JavaSql::SQLException
|
150
|
-
end
|
151
|
-
|
152
|
-
private
|
153
|
-
|
154
|
-
def client
|
155
|
-
@client ||= java.sql.DriverManager.getConnection address,
|
156
|
-
options[:username], options[:password]
|
157
|
-
rescue base_error => error
|
158
|
-
raise ThinkingSphinx::SphinxError.new_from_mysql error
|
159
|
-
end
|
160
|
-
|
161
|
-
def results_for(statements)
|
162
|
-
statement = client.createStatement
|
163
|
-
statement.execute statements
|
164
|
-
|
165
|
-
results = [set_to_array(statement.getResultSet)]
|
166
|
-
results << set_to_array(statement.getResultSet) while statement.getMoreResults
|
167
|
-
results.compact
|
168
|
-
end
|
169
|
-
|
170
|
-
def set_to_array(set)
|
171
|
-
return nil if set.nil?
|
172
|
-
|
173
|
-
meta = set.getMetaData
|
174
|
-
rows = []
|
175
|
-
|
176
|
-
while set.next
|
177
|
-
rows << (1..meta.getColumnCount).inject({}) do |row, index|
|
178
|
-
name = meta.getColumnName index
|
179
|
-
row[name] = set.getObject(index)
|
180
|
-
row
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
rows
|
185
|
-
end
|
186
|
-
end
|
187
66
|
end
|
67
|
+
|
68
|
+
require 'thinking_sphinx/connection/client'
|
69
|
+
require 'thinking_sphinx/connection/jruby'
|
70
|
+
require 'thinking_sphinx/connection/mri'
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class ThinkingSphinx::Connection::Client
|
2
|
+
def close
|
3
|
+
close! unless ThinkingSphinx::Connection.persistent?
|
4
|
+
end
|
5
|
+
|
6
|
+
def close!
|
7
|
+
client.close
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute(statement)
|
11
|
+
check_and_perform(statement).first
|
12
|
+
end
|
13
|
+
|
14
|
+
def query_all(*statements)
|
15
|
+
check_and_perform statements.join('; ')
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def check(statements)
|
21
|
+
if statements.length > ThinkingSphinx::MAXIMUM_STATEMENT_LENGTH
|
22
|
+
exception = ThinkingSphinx::QueryLengthError.new
|
23
|
+
exception.statement = statements
|
24
|
+
raise exception
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_and_perform(statements)
|
29
|
+
check statements
|
30
|
+
perform statements
|
31
|
+
end
|
32
|
+
|
33
|
+
def close_and_clear
|
34
|
+
client.close
|
35
|
+
@client = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def perform(statements)
|
39
|
+
results_for statements
|
40
|
+
rescue => error
|
41
|
+
message = "#{error.message} - #{statements}"
|
42
|
+
wrapper = ThinkingSphinx::QueryExecutionError.new message
|
43
|
+
wrapper.statement = statements
|
44
|
+
raise wrapper
|
45
|
+
ensure
|
46
|
+
close_and_clear unless ThinkingSphinx::Connection.persistent?
|
47
|
+
end
|
48
|
+
end
|