thinking-sphinx 3.1.1 → 3.1.2
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 +6 -6
- data/HISTORY +21 -0
- data/lib/thinking_sphinx.rb +1 -0
- data/lib/thinking_sphinx/active_record.rb +1 -0
- data/lib/thinking_sphinx/active_record/base.rb +3 -2
- data/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb +4 -0
- data/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb +4 -0
- data/lib/thinking_sphinx/active_record/filtered_reflection.rb +10 -2
- data/lib/thinking_sphinx/active_record/interpreter.rb +2 -1
- data/lib/thinking_sphinx/active_record/join_association.rb +13 -0
- data/lib/thinking_sphinx/active_record/log_subscriber.rb +8 -3
- data/lib/thinking_sphinx/active_record/polymorpher.rb +8 -1
- data/lib/thinking_sphinx/active_record/sql_builder.rb +19 -4
- data/lib/thinking_sphinx/active_record/sql_builder/statement.rb +19 -12
- data/lib/thinking_sphinx/active_record/sql_source.rb +2 -2
- data/lib/thinking_sphinx/capistrano/v3.rb +1 -1
- data/lib/thinking_sphinx/configuration.rb +1 -1
- data/lib/thinking_sphinx/connection.rb +4 -2
- data/lib/thinking_sphinx/controller.rb +3 -13
- data/lib/thinking_sphinx/core/index.rb +13 -3
- data/lib/thinking_sphinx/core/interpreter.rb +4 -0
- data/lib/thinking_sphinx/deletion.rb +5 -3
- data/lib/thinking_sphinx/errors.rb +3 -0
- data/lib/thinking_sphinx/facet.rb +3 -2
- data/lib/thinking_sphinx/facet_search.rb +6 -2
- data/lib/thinking_sphinx/guard.rb +6 -0
- data/lib/thinking_sphinx/guard/file.rb +26 -0
- data/lib/thinking_sphinx/guard/files.rb +38 -0
- data/lib/thinking_sphinx/masks/group_enumerators_mask.rb +4 -4
- data/lib/thinking_sphinx/masks/weight_enumerator_mask.rb +1 -1
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +2 -2
- data/lib/thinking_sphinx/panes/weight_pane.rb +1 -1
- data/lib/thinking_sphinx/rake_interface.rb +20 -1
- data/lib/thinking_sphinx/real_time.rb +2 -2
- data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +10 -4
- data/lib/thinking_sphinx/real_time/interpreter.rb +2 -1
- data/lib/thinking_sphinx/real_time/transcriber.rb +1 -1
- data/lib/thinking_sphinx/search.rb +4 -0
- data/lib/thinking_sphinx/search/merger.rb +4 -0
- data/lib/thinking_sphinx/sphinxql.rb +12 -6
- data/lib/thinking_sphinx/tasks.rb +13 -3
- data/spec/acceptance/attribute_access_spec.rb +2 -2
- data/spec/acceptance/big_integers_spec.rb +11 -0
- data/spec/acceptance/facets_spec.rb +4 -1
- data/spec/acceptance/indexing_spec.rb +9 -0
- data/spec/acceptance/specifying_sql_spec.rb +1 -1
- data/spec/acceptance/sphinx_scopes_spec.rb +9 -0
- data/spec/internal/app/indices/user_index.rb +2 -0
- data/spec/thinking_sphinx/active_record/base_spec.rb +3 -1
- data/spec/thinking_sphinx/active_record/index_spec.rb +8 -0
- data/spec/thinking_sphinx/active_record/interpreter_spec.rb +7 -1
- data/spec/thinking_sphinx/active_record/polymorpher_spec.rb +16 -3
- data/spec/thinking_sphinx/configuration_spec.rb +18 -0
- data/spec/thinking_sphinx/facet_search_spec.rb +6 -6
- data/spec/thinking_sphinx/masks/scopes_mask_spec.rb +6 -1
- data/spec/thinking_sphinx/panes/weight_pane_spec.rb +1 -1
- data/spec/thinking_sphinx/rake_interface_spec.rb +62 -3
- data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +38 -4
- data/spec/thinking_sphinx/real_time/index_spec.rb +4 -0
- data/spec/thinking_sphinx/real_time/interpreter_spec.rb +7 -1
- data/thinking-sphinx.gemspec +1 -1
- metadata +7 -3
@@ -13,6 +13,10 @@ class ThinkingSphinx::Core::Interpreter < BasicObject
|
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
+
def search_option?(key)
|
17
|
+
::ThinkingSphinx::Middlewares::SphinxQL::SELECT_OPTIONS.include? key
|
18
|
+
end
|
19
|
+
|
16
20
|
def method_missing(method, *args)
|
17
21
|
::ThinkingSphinx::ActiveRecord::Column.new method, *args
|
18
22
|
end
|
@@ -38,11 +38,13 @@ class ThinkingSphinx::Deletion
|
|
38
38
|
|
39
39
|
class PlainDeletion < ThinkingSphinx::Deletion
|
40
40
|
def perform
|
41
|
-
|
41
|
+
document_ids_for_keys.each_slice(1000) do |document_ids|
|
42
|
+
execute <<-SQL
|
42
43
|
UPDATE #{name}
|
43
44
|
SET sphinx_deleted = 1
|
44
|
-
WHERE id IN (#{
|
45
|
-
|
45
|
+
WHERE id IN (#{document_ids.join(', ')})
|
46
|
+
SQL
|
47
|
+
end
|
46
48
|
end
|
47
49
|
end
|
48
50
|
end
|
@@ -11,7 +11,7 @@ class ThinkingSphinx::Facet
|
|
11
11
|
|
12
12
|
def results_from(raw)
|
13
13
|
raw.inject({}) { |hash, row|
|
14
|
-
hash[row[group_column]] = row[
|
14
|
+
hash[row[group_column]] = row[ThinkingSphinx::SphinxQL.count[:column]]
|
15
15
|
hash
|
16
16
|
}
|
17
17
|
end
|
@@ -19,7 +19,8 @@ class ThinkingSphinx::Facet
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def group_column
|
22
|
-
@properties.any?(&:multi?) ?
|
22
|
+
@properties.any?(&:multi?) ?
|
23
|
+
ThinkingSphinx::SphinxQL.group_by[:column] : name
|
23
24
|
end
|
24
25
|
|
25
26
|
def use_field?
|
@@ -51,6 +51,10 @@ class ThinkingSphinx::FacetSearch
|
|
51
51
|
@populated = true
|
52
52
|
end
|
53
53
|
|
54
|
+
def populated?
|
55
|
+
@populated
|
56
|
+
end
|
57
|
+
|
54
58
|
def to_hash
|
55
59
|
populate
|
56
60
|
|
@@ -102,8 +106,8 @@ class ThinkingSphinx::FacetSearch
|
|
102
106
|
def options_for(facet)
|
103
107
|
options.merge(
|
104
108
|
:select => [(options[:select] || '*'),
|
105
|
-
"#{ThinkingSphinx::SphinxQL.group_by}
|
106
|
-
"#{ThinkingSphinx::SphinxQL.count}
|
109
|
+
"#{ThinkingSphinx::SphinxQL.group_by[:select]}",
|
110
|
+
"#{ThinkingSphinx::SphinxQL.count[:select]}"
|
107
111
|
].join(', '),
|
108
112
|
:group_by => facet.name,
|
109
113
|
:indices => index_names_for(facet),
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class ThinkingSphinx::Guard::File
|
2
|
+
attr_reader :name
|
3
|
+
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
end
|
7
|
+
|
8
|
+
def lock
|
9
|
+
FileUtils.touch path
|
10
|
+
end
|
11
|
+
|
12
|
+
def locked?
|
13
|
+
File.exists? path
|
14
|
+
end
|
15
|
+
|
16
|
+
def path
|
17
|
+
@path ||= File.join(
|
18
|
+
ThinkingSphinx::Configuration.instance.indices_location,
|
19
|
+
"ts-#{name}.tmp"
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def unlock
|
24
|
+
FileUtils.rm path
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class ThinkingSphinx::Guard::Files
|
2
|
+
def self.call(names, &block)
|
3
|
+
new(names).call(&block)
|
4
|
+
end
|
5
|
+
|
6
|
+
def initialize(names)
|
7
|
+
@names = names
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(&block)
|
11
|
+
return if unlocked.empty?
|
12
|
+
|
13
|
+
unlocked.each &:lock
|
14
|
+
block.call unlocked.collect(&:name)
|
15
|
+
rescue => error
|
16
|
+
raise error
|
17
|
+
ensure
|
18
|
+
unlocked.each &:unlock
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :names
|
24
|
+
|
25
|
+
def log_lock(file)
|
26
|
+
ThinkingSphinx::Logger.log :guard,
|
27
|
+
"Guard file for index #{file.name} exists, not indexing: #{file.path}."
|
28
|
+
end
|
29
|
+
|
30
|
+
def unlocked
|
31
|
+
@unlocked ||= names.collect { |name|
|
32
|
+
ThinkingSphinx::Guard::File.new name
|
33
|
+
}.reject { |file|
|
34
|
+
log_lock file if file.locked?
|
35
|
+
file.locked?
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
@@ -9,20 +9,20 @@ class ThinkingSphinx::Masks::GroupEnumeratorsMask
|
|
9
9
|
|
10
10
|
def each_with_count(&block)
|
11
11
|
@search.raw.each_with_index do |row, index|
|
12
|
-
yield @search[index], row[
|
12
|
+
yield @search[index], row[ThinkingSphinx::SphinxQL.count[:column]]
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
def each_with_group(&block)
|
17
17
|
@search.raw.each_with_index do |row, index|
|
18
|
-
yield @search[index], row[
|
18
|
+
yield @search[index], row[ThinkingSphinx::SphinxQL.group_by[:column]]
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def each_with_group_and_count(&block)
|
23
23
|
@search.raw.each_with_index do |row, index|
|
24
|
-
yield @search[index], row[
|
25
|
-
row[
|
24
|
+
yield @search[index], row[ThinkingSphinx::SphinxQL.group_by[:column]],
|
25
|
+
row[ThinkingSphinx::SphinxQL.count[:column]]
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -9,7 +9,7 @@ class ThinkingSphinx::Masks::WeightEnumeratorMask
|
|
9
9
|
|
10
10
|
def each_with_weight(&block)
|
11
11
|
@search.raw.each_with_index do |row, index|
|
12
|
-
yield @search[index], row[ThinkingSphinx::SphinxQL.weight]
|
12
|
+
yield @search[index], row[ThinkingSphinx::SphinxQL.weight[:column]]
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -157,8 +157,8 @@ SQL
|
|
157
157
|
|
158
158
|
def values
|
159
159
|
options[:select] ||= ['*',
|
160
|
-
"#{ThinkingSphinx::SphinxQL.group_by}
|
161
|
-
"#{ThinkingSphinx::SphinxQL.count}
|
160
|
+
"#{ThinkingSphinx::SphinxQL.group_by[:select]}",
|
161
|
+
"#{ThinkingSphinx::SphinxQL.count[:select]}"
|
162
162
|
].join(', ') if group_attribute.present?
|
163
163
|
options[:select]
|
164
164
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class ThinkingSphinx::RakeInterface
|
2
|
-
def
|
2
|
+
def clear_all
|
3
3
|
[
|
4
4
|
configuration.indices_location,
|
5
5
|
configuration.searchd.binlog_path
|
@@ -8,6 +8,17 @@ class ThinkingSphinx::RakeInterface
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
def clear_real_time
|
12
|
+
indices = configuration.indices.select { |index| index.type == 'rt' }
|
13
|
+
indices.each do |index|
|
14
|
+
index.render
|
15
|
+
Dir["#{index.path}.*"].each { |path| FileUtils.rm path }
|
16
|
+
end
|
17
|
+
|
18
|
+
path = configuration.searchd.binlog_path
|
19
|
+
FileUtils.rm_r(path) if File.exists?(path)
|
20
|
+
end
|
21
|
+
|
11
22
|
def configure
|
12
23
|
puts "Generating configuration to #{configuration.configuration_file}"
|
13
24
|
configuration.render_to_file
|
@@ -47,6 +58,14 @@ class ThinkingSphinx::RakeInterface
|
|
47
58
|
end
|
48
59
|
end
|
49
60
|
|
61
|
+
def status
|
62
|
+
if controller.running?
|
63
|
+
puts "The Sphinx daemon searchd is currently running."
|
64
|
+
else
|
65
|
+
puts "The Sphinx daemon searchd is not currently running."
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
50
69
|
def stop
|
51
70
|
unless controller.running?
|
52
71
|
puts 'searchd is not currently running.' and return
|
@@ -3,8 +3,8 @@ module ThinkingSphinx::RealTime
|
|
3
3
|
#
|
4
4
|
end
|
5
5
|
|
6
|
-
def self.callback_for(reference, path = [])
|
7
|
-
Callbacks::RealTimeCallbacks.new reference, path
|
6
|
+
def self.callback_for(reference, path = [], &block)
|
7
|
+
Callbacks::RealTimeCallbacks.new reference, path, &block
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks
|
2
|
-
def initialize(reference, path = [])
|
3
|
-
@reference, @path = reference, path
|
2
|
+
def initialize(reference, path = [], &block)
|
3
|
+
@reference, @path, @block = reference, path, block
|
4
4
|
end
|
5
5
|
|
6
6
|
def after_save(instance)
|
@@ -15,7 +15,7 @@ class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks
|
|
15
15
|
|
16
16
|
private
|
17
17
|
|
18
|
-
attr_reader :reference, :path
|
18
|
+
attr_reader :reference, :path, :block
|
19
19
|
|
20
20
|
def callbacks_enabled?
|
21
21
|
setting = configuration.settings['real_time_callbacks']
|
@@ -31,7 +31,13 @@ class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def objects_for(instance)
|
34
|
-
|
34
|
+
if block
|
35
|
+
results = block.call instance
|
36
|
+
else
|
37
|
+
results = path.inject(instance) { |object, method| object.send method }
|
38
|
+
end
|
39
|
+
|
40
|
+
Array results
|
35
41
|
end
|
36
42
|
|
37
43
|
def real_time_indices?
|
@@ -23,7 +23,8 @@ class ThinkingSphinx::RealTime::Interpreter <
|
|
23
23
|
|
24
24
|
def set_property(properties)
|
25
25
|
properties.each do |key, value|
|
26
|
-
@index.send("#{key}=", value)
|
26
|
+
@index.send("#{key}=", value) if @index.class.settings.include?(key)
|
27
|
+
@index.options[key] = value if search_option?(key)
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
@@ -4,7 +4,7 @@ class ThinkingSphinx::RealTime::Transcriber
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def copy(instance)
|
7
|
-
return unless copy?
|
7
|
+
return unless instance.persisted? && copy?(instance)
|
8
8
|
|
9
9
|
columns, values = ['id'], [index.document_id_for_key(instance.id)]
|
10
10
|
(index.fields + index.attributes).each do |property|
|
@@ -6,6 +6,10 @@ class ThinkingSphinx::Search::Merger
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def merge!(query = nil, options = {})
|
9
|
+
if search.populated?
|
10
|
+
raise ThinkingSphinx::PopulatedResultsError, 'This search request has already been made - you can no longer modify it.'
|
11
|
+
end
|
12
|
+
|
9
13
|
query, options = nil, query if query.is_a?(Hash)
|
10
14
|
@search.query = query unless query.nil?
|
11
15
|
|
@@ -2,15 +2,21 @@ module ThinkingSphinx::SphinxQL
|
|
2
2
|
mattr_accessor :weight, :group_by, :count
|
3
3
|
|
4
4
|
def self.functions!
|
5
|
-
self.weight = 'weight()'
|
6
|
-
self.group_by =
|
7
|
-
|
5
|
+
self.weight = {:select => 'weight()', :column => 'weight()'}
|
6
|
+
self.group_by = {
|
7
|
+
:select => 'groupby() AS sphinx_internal_group',
|
8
|
+
:column => 'sphinx_internal_group'
|
9
|
+
}
|
10
|
+
self.count = {
|
11
|
+
:select => 'id AS sphinx_document_id, count(DISTINCT sphinx_document_id) AS sphinx_internal_count',
|
12
|
+
:column => 'sphinx_internal_count'
|
13
|
+
}
|
8
14
|
end
|
9
15
|
|
10
16
|
def self.variables!
|
11
|
-
self.weight = '@weight'
|
12
|
-
self.group_by = '@groupby'
|
13
|
-
self.count = '@count'
|
17
|
+
self.weight = {:select => '@weight', :column => '@weight'}
|
18
|
+
self.group_by = {:select => '@groupby', :column => '@groupby'}
|
19
|
+
self.count = {:select => '@count', :column => '@count'}
|
14
20
|
end
|
15
21
|
|
16
22
|
self.functions!
|
@@ -14,7 +14,12 @@ namespace :ts do
|
|
14
14
|
|
15
15
|
desc 'Clear out Sphinx files'
|
16
16
|
task :clear => :environment do
|
17
|
-
interface.
|
17
|
+
interface.clear_all
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'Clear out real-time index files'
|
21
|
+
task :clear_rt => :environment do
|
22
|
+
interface.clear_real_time
|
18
23
|
end
|
19
24
|
|
20
25
|
desc 'Generate fresh index files for real-time indices'
|
@@ -24,10 +29,10 @@ namespace :ts do
|
|
24
29
|
end
|
25
30
|
|
26
31
|
desc 'Stop Sphinx, index and then restart Sphinx'
|
27
|
-
task :rebuild => [:stop, :index, :start]
|
32
|
+
task :rebuild => [:stop, :clear, :index, :start]
|
28
33
|
|
29
34
|
desc 'Stop Sphinx, clear files, reconfigure, start Sphinx, generate files'
|
30
|
-
task :regenerate => [:stop, :
|
35
|
+
task :regenerate => [:stop, :clear_rt, :configure, :start, :generate]
|
31
36
|
|
32
37
|
desc 'Restart the Sphinx daemon'
|
33
38
|
task :restart => [:stop, :start]
|
@@ -42,6 +47,11 @@ namespace :ts do
|
|
42
47
|
interface.stop
|
43
48
|
end
|
44
49
|
|
50
|
+
desc 'Determine whether Sphinx is running'
|
51
|
+
task :status => :environment do
|
52
|
+
interface.status
|
53
|
+
end
|
54
|
+
|
45
55
|
def interface
|
46
56
|
@interface ||= ThinkingSphinx::RakeInterface.new
|
47
57
|
end
|
@@ -16,7 +16,7 @@ describe 'Accessing attributes directly via search results', :live => true do
|
|
16
16
|
index
|
17
17
|
|
18
18
|
search = Book.search 'gods',
|
19
|
-
:select => "*, #{ThinkingSphinx::SphinxQL.weight}"
|
19
|
+
:select => "*, #{ThinkingSphinx::SphinxQL.weight[:select]}"
|
20
20
|
search.context[:panes] << ThinkingSphinx::Panes::WeightPane
|
21
21
|
|
22
22
|
search.first.weight.should == 2500
|
@@ -27,7 +27,7 @@ describe 'Accessing attributes directly via search results', :live => true do
|
|
27
27
|
index
|
28
28
|
|
29
29
|
search = Book.search 'gods',
|
30
|
-
:select => "*, #{ThinkingSphinx::SphinxQL.weight}"
|
30
|
+
:select => "*, #{ThinkingSphinx::SphinxQL.weight[:select]}"
|
31
31
|
search.masks << ThinkingSphinx::Masks::WeightEnumeratorMask
|
32
32
|
|
33
33
|
expectations = [[gods, 2500]]
|
@@ -25,3 +25,14 @@ describe '64 bit integer support' do
|
|
25
25
|
}.type.should == :bigint
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
describe '64 bit document ids', :live => true do
|
30
|
+
it 'handles large 32 bit integers with an offset multiplier' do
|
31
|
+
user = User.create! :name => 'Pat'
|
32
|
+
user.update_column :id, 980190962
|
33
|
+
|
34
|
+
index
|
35
|
+
|
36
|
+
expect(User.search('pat').to_a).to eq([user])
|
37
|
+
end
|
38
|
+
end
|
@@ -26,10 +26,13 @@ describe 'Faceted searching', :live => true do
|
|
26
26
|
Tee.create!
|
27
27
|
City.create!
|
28
28
|
Product.create!
|
29
|
+
Article.create!
|
29
30
|
index
|
30
31
|
|
32
|
+
article_count = ENV['SPHINX_VERSION'].try(:[], /2.0.\d/) ? 2 : 1
|
33
|
+
|
31
34
|
ThinkingSphinx.facets.to_hash[:class].should == {
|
32
|
-
'Tee' => 2, 'City' => 1, 'Product' => 1
|
35
|
+
'Tee' => 2, 'City' => 1, 'Product' => 1, 'Article' => article_count
|
33
36
|
}
|
34
37
|
end
|
35
38
|
|