thinking-sphinx 3.3.0 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|