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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +29 -20
  4. data/Appraisals +9 -5
  5. data/Gemfile +8 -3
  6. data/HISTORY +24 -0
  7. data/README.textile +5 -4
  8. data/bin/console +14 -0
  9. data/bin/literals +9 -0
  10. data/bin/loadsphinx +38 -0
  11. data/lib/thinking_sphinx.rb +15 -2
  12. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +2 -3
  13. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +11 -1
  14. data/lib/thinking_sphinx/active_record/index.rb +1 -1
  15. data/lib/thinking_sphinx/active_record/join_association.rb +3 -1
  16. data/lib/thinking_sphinx/active_record/log_subscriber.rb +5 -0
  17. data/lib/thinking_sphinx/active_record/sql_source.rb +1 -1
  18. data/lib/thinking_sphinx/attribute_types.rb +70 -0
  19. data/lib/thinking_sphinx/commands/base.rb +41 -0
  20. data/lib/thinking_sphinx/commands/configure.rb +13 -0
  21. data/lib/thinking_sphinx/commands/index.rb +11 -0
  22. data/lib/thinking_sphinx/commands/start_attached.rb +20 -0
  23. data/lib/thinking_sphinx/commands/start_detached.rb +19 -0
  24. data/lib/thinking_sphinx/commands/stop.rb +22 -0
  25. data/lib/thinking_sphinx/configuration.rb +36 -28
  26. data/lib/thinking_sphinx/configuration/minimum_fields.rb +11 -8
  27. data/lib/thinking_sphinx/connection.rb +5 -122
  28. data/lib/thinking_sphinx/connection/client.rb +48 -0
  29. data/lib/thinking_sphinx/connection/jruby.rb +53 -0
  30. data/lib/thinking_sphinx/connection/mri.rb +28 -0
  31. data/lib/thinking_sphinx/core/index.rb +11 -0
  32. data/lib/thinking_sphinx/deletion.rb +6 -2
  33. data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
  34. data/lib/thinking_sphinx/deltas/delete_job.rb +14 -4
  35. data/lib/thinking_sphinx/distributed/index.rb +10 -0
  36. data/lib/thinking_sphinx/errors.rb +1 -1
  37. data/lib/thinking_sphinx/index_set.rb +14 -2
  38. data/lib/thinking_sphinx/interfaces/daemon.rb +32 -0
  39. data/lib/thinking_sphinx/interfaces/real_time.rb +41 -0
  40. data/lib/thinking_sphinx/interfaces/sql.rb +41 -0
  41. data/lib/thinking_sphinx/middlewares.rb +5 -3
  42. data/lib/thinking_sphinx/middlewares/active_record_translator.rb +13 -6
  43. data/lib/thinking_sphinx/middlewares/attribute_typer.rb +48 -0
  44. data/lib/thinking_sphinx/middlewares/valid_options.rb +23 -0
  45. data/lib/thinking_sphinx/rake_interface.rb +10 -124
  46. data/lib/thinking_sphinx/search.rb +11 -0
  47. data/lib/thinking_sphinx/search/query.rb +7 -1
  48. data/lib/thinking_sphinx/tasks.rb +80 -21
  49. data/lib/thinking_sphinx/with_output.rb +11 -0
  50. data/spec/acceptance/connection_spec.rb +4 -4
  51. data/spec/acceptance/searching_within_a_model_spec.rb +7 -0
  52. data/spec/acceptance/specifying_sql_spec.rb +26 -8
  53. data/spec/acceptance/sql_deltas_spec.rb +12 -0
  54. data/spec/internal/app/indices/album_index.rb +3 -0
  55. data/spec/internal/app/models/album.rb +19 -0
  56. data/spec/internal/db/schema.rb +8 -0
  57. data/spec/spec_helper.rb +4 -0
  58. data/spec/support/json_column.rb +5 -1
  59. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +5 -1
  60. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +6 -0
  61. data/spec/thinking_sphinx/attribute_types_spec.rb +50 -0
  62. data/spec/thinking_sphinx/commands/configure_spec.rb +29 -0
  63. data/spec/thinking_sphinx/commands/index_spec.rb +26 -0
  64. data/spec/thinking_sphinx/commands/start_detached_spec.rb +55 -0
  65. data/spec/thinking_sphinx/commands/stop_spec.rb +54 -0
  66. data/spec/thinking_sphinx/configuration/minimum_fields_spec.rb +36 -0
  67. data/spec/thinking_sphinx/deletion_spec.rb +2 -5
  68. data/spec/thinking_sphinx/deltas/default_delta_spec.rb +1 -1
  69. data/spec/thinking_sphinx/errors_spec.rb +7 -0
  70. data/spec/thinking_sphinx/index_set_spec.rb +30 -7
  71. data/spec/thinking_sphinx/interfaces/daemon_spec.rb +52 -0
  72. data/spec/thinking_sphinx/interfaces/real_time_spec.rb +109 -0
  73. data/spec/thinking_sphinx/interfaces/sql_spec.rb +98 -0
  74. data/spec/thinking_sphinx/middlewares/attribute_typer_spec.rb +42 -0
  75. data/spec/thinking_sphinx/middlewares/valid_options_spec.rb +49 -0
  76. data/spec/thinking_sphinx/rake_interface_spec.rb +13 -246
  77. data/spec/thinking_sphinx/search/query_spec.rb +7 -0
  78. data/thinking-sphinx.gemspec +5 -4
  79. metadata +72 -16
  80. data/gemfiles/.gitignore +0 -1
  81. data/gemfiles/rails_3_2.gemfile +0 -13
  82. data/gemfiles/rails_4_0.gemfile +0 -13
  83. data/gemfiles/rails_4_1.gemfile +0 -13
  84. data/gemfiles/rails_4_2.gemfile +0 -13
  85. 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,13 @@
1
+ class ThinkingSphinx::Commands::Configure < ThinkingSphinx::Commands::Base
2
+ def call
3
+ log "Generating configuration to #{configuration.configuration_file}"
4
+
5
+ configuration.render_to_file
6
+ end
7
+
8
+ private
9
+
10
+ def type
11
+ 'configure'
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ class ThinkingSphinx::Commands::Index < ThinkingSphinx::Commands::Base
2
+ def call
3
+ controller.index :verbose => options[:verbose]
4
+ end
5
+
6
+ private
7
+
8
+ def type
9
+ 'indexing'
10
+ end
11
+ 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
- if settings['distributed_indices'].nil? || settings['distributed_indices']
88
- ThinkingSphinx::Configuration::DistributedIndices.new(indices).reconcile
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 framework_root
161
- Pathname.new(framework.root)
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 settings_to_hash
170
- input = File.read settings_file
171
- input = ERB.new(input).result if defined?(ERB)
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 reset
182
- @settings = nil
183
- setup
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
- def tmp_path
187
- real_path 'tmp'
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 apply_sphinx_settings!
197
- sphinx_sections.each do |object|
198
- settings.each do |key, value|
199
- next unless object.class.settings.include?(key.to_sym)
206
+ def tmp_path
207
+ real_path 'tmp'
208
+ end
200
209
 
201
- object.send("#{key}=", value)
202
- end
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
- sources.each do |source|
10
- source.fields.delete_if do |field|
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