thinking-sphinx 4.3.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +16 -21
- data/Appraisals +2 -17
- data/CHANGELOG.markdown +62 -1
- data/README.textile +14 -16
- data/bin/loadsphinx +20 -5
- data/lib/thinking_sphinx/active_record/association_proxy.rb +1 -2
- data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
- data/lib/thinking_sphinx/active_record/base.rb +17 -6
- data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -1
- data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +3 -2
- data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
- data/lib/thinking_sphinx/active_record/interpreter.rb +4 -4
- data/lib/thinking_sphinx/active_record/sql_source.rb +12 -0
- data/lib/thinking_sphinx/active_record/sql_source/template.rb +2 -2
- data/lib/thinking_sphinx/callbacks.rb +9 -0
- data/lib/thinking_sphinx/callbacks/appender.rb +47 -0
- data/lib/thinking_sphinx/commands/index_real_time.rb +1 -3
- data/lib/thinking_sphinx/configuration.rb +9 -3
- data/lib/thinking_sphinx/core/index.rb +5 -2
- data/lib/thinking_sphinx/deletion.rb +18 -17
- data/lib/thinking_sphinx/index_set.rb +7 -3
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +1 -1
- data/lib/thinking_sphinx/railtie.rb +9 -1
- data/lib/thinking_sphinx/real_time.rb +17 -0
- data/lib/thinking_sphinx/real_time/index.rb +4 -0
- data/lib/thinking_sphinx/real_time/interpreter.rb +8 -6
- data/lib/thinking_sphinx/real_time/populator.rb +1 -1
- data/lib/thinking_sphinx/real_time/processor.rb +36 -0
- data/lib/thinking_sphinx/real_time/transcriber.rb +35 -19
- data/lib/thinking_sphinx/subscribers/populator_subscriber.rb +0 -4
- data/spec/acceptance/big_integers_spec.rb +1 -1
- data/spec/acceptance/merging_spec.rb +1 -1
- data/spec/acceptance/real_time_updates_spec.rb +2 -2
- data/spec/acceptance/sql_deltas_spec.rb +3 -3
- data/spec/acceptance/suspended_deltas_spec.rb +3 -3
- data/spec/internal/app/models/admin/person.rb +3 -1
- data/spec/internal/app/models/album.rb +3 -1
- data/spec/internal/app/models/animal.rb +1 -0
- data/spec/internal/app/models/article.rb +2 -0
- data/spec/internal/app/models/bird.rb +1 -0
- data/spec/internal/app/models/book.rb +2 -0
- data/spec/internal/app/models/car.rb +1 -1
- data/spec/internal/app/models/city.rb +2 -0
- data/spec/internal/app/models/product.rb +1 -1
- data/spec/internal/app/models/tee.rb +2 -0
- data/spec/internal/app/models/user.rb +2 -0
- data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +2 -1
- data/spec/thinking_sphinx/active_record/index_spec.rb +0 -12
- data/spec/thinking_sphinx/active_record/interpreter_spec.rb +15 -14
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +38 -0
- data/spec/thinking_sphinx/configuration_spec.rb +17 -16
- data/spec/thinking_sphinx/deletion_spec.rb +5 -4
- data/spec/thinking_sphinx/index_set_spec.rb +28 -12
- data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +2 -1
- data/spec/thinking_sphinx/real_time/index_spec.rb +38 -12
- data/spec/thinking_sphinx/real_time/interpreter_spec.rb +14 -14
- data/spec/thinking_sphinx/real_time/transcriber_spec.rb +9 -1
- data/thinking-sphinx.gemspec +3 -5
- metadata +9 -8
@@ -3,6 +3,13 @@
|
|
3
3
|
class ThinkingSphinx::Callbacks
|
4
4
|
attr_reader :instance
|
5
5
|
|
6
|
+
def self.append(model, reference = nil, options, &block)
|
7
|
+
reference ||= ThinkingSphinx::Configuration.instance.index_set_class.
|
8
|
+
reference_name(model)
|
9
|
+
|
10
|
+
ThinkingSphinx::Callbacks::Appender.call(model, reference, options, &block)
|
11
|
+
end
|
12
|
+
|
6
13
|
def self.callbacks(*methods)
|
7
14
|
mod = Module.new
|
8
15
|
methods.each do |method|
|
@@ -33,3 +40,5 @@ class ThinkingSphinx::Callbacks
|
|
33
40
|
@instance = instance
|
34
41
|
end
|
35
42
|
end
|
43
|
+
|
44
|
+
require "thinking_sphinx/callbacks/appender"
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ThinkingSphinx::Callbacks::Appender
|
4
|
+
def self.call(model, reference, options, &block)
|
5
|
+
new(model, reference, options, &block).call
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(model, reference, options, &block)
|
9
|
+
@model = model
|
10
|
+
@reference = reference
|
11
|
+
@options = options
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
model.after_destroy ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks
|
17
|
+
|
18
|
+
if behaviours.include?(:deltas)
|
19
|
+
model.before_save ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks
|
20
|
+
model.after_commit ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks
|
21
|
+
end
|
22
|
+
|
23
|
+
if behaviours.include?(:real_time)
|
24
|
+
model.after_save ThinkingSphinx::RealTime.callback_for(
|
25
|
+
reference, path, &block
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
if behaviours.include?(:updates)
|
30
|
+
model.after_update(
|
31
|
+
ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :model, :reference, :options, :block
|
39
|
+
|
40
|
+
def behaviours
|
41
|
+
options[:behaviours] || []
|
42
|
+
end
|
43
|
+
|
44
|
+
def path
|
45
|
+
options[:path] || []
|
46
|
+
end
|
47
|
+
end
|
@@ -2,9 +2,7 @@
|
|
2
2
|
|
3
3
|
class ThinkingSphinx::Commands::IndexRealTime < ThinkingSphinx::Commands::Base
|
4
4
|
def call
|
5
|
-
options[:indices]
|
6
|
-
ThinkingSphinx::RealTime::Populator.populate index
|
7
|
-
|
5
|
+
ThinkingSphinx::RealTime.processor.call options[:indices] do
|
8
6
|
command :rotate
|
9
7
|
end
|
10
8
|
end
|
@@ -87,9 +87,7 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
87
87
|
return if @preloaded_indices
|
88
88
|
|
89
89
|
index_paths.each do |path|
|
90
|
-
Dir["#{path}/**/*.rb"].sort.each
|
91
|
-
ActiveSupport::Dependencies.require_or_load file
|
92
|
-
end
|
90
|
+
Dir["#{path}/**/*.rb"].sort.each { |file| preload_index file }
|
93
91
|
end
|
94
92
|
|
95
93
|
normalise
|
@@ -99,6 +97,14 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
|
|
99
97
|
end
|
100
98
|
end
|
101
99
|
|
100
|
+
def preload_index(file)
|
101
|
+
if ActiveRecord::VERSION::MAJOR <= 5
|
102
|
+
ActiveSupport::Dependencies.require_or_load file
|
103
|
+
else
|
104
|
+
load file
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
102
108
|
def render
|
103
109
|
preload_indices
|
104
110
|
|
@@ -11,7 +11,6 @@ module ThinkingSphinx::Core::Index
|
|
11
11
|
|
12
12
|
def initialize(reference, options = {})
|
13
13
|
@reference = reference.to_sym
|
14
|
-
@docinfo = :extern unless config.settings["skip_docinfo"]
|
15
14
|
@options = options
|
16
15
|
@offset = config.next_offset(options[:offset_as] || reference)
|
17
16
|
@type = 'plain'
|
@@ -38,7 +37,11 @@ module ThinkingSphinx::Core::Index
|
|
38
37
|
end
|
39
38
|
|
40
39
|
def interpret_definition!
|
41
|
-
|
40
|
+
table_exists = model.table_exists?
|
41
|
+
unless table_exists
|
42
|
+
Rails.logger.info "No table exists for #{model}. Index can not be created"
|
43
|
+
return
|
44
|
+
end
|
42
45
|
return if @interpreted_definition
|
43
46
|
|
44
47
|
apply_defaults!
|
@@ -22,10 +22,6 @@ class ThinkingSphinx::Deletion
|
|
22
22
|
|
23
23
|
attr_reader :index, :ids
|
24
24
|
|
25
|
-
def document_ids_for_keys
|
26
|
-
ids.collect { |id| index.document_id_for_key id }
|
27
|
-
end
|
28
|
-
|
29
25
|
def execute(statement)
|
30
26
|
statement = statement.gsub(/\s*\n\s*/, ' ').strip
|
31
27
|
|
@@ -36,11 +32,28 @@ class ThinkingSphinx::Deletion
|
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
35
|
+
class PlainDeletion < ThinkingSphinx::Deletion
|
36
|
+
def perform
|
37
|
+
ids.each_slice(1000) do |some_ids|
|
38
|
+
execute <<-SQL
|
39
|
+
UPDATE #{name}
|
40
|
+
SET sphinx_deleted = 1
|
41
|
+
WHERE sphinx_internal_id IN (#{some_ids.join(', ')})
|
42
|
+
SQL
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
39
47
|
class RealtimeDeletion < ThinkingSphinx::Deletion
|
40
48
|
def perform
|
41
49
|
return unless callbacks_enabled?
|
42
50
|
|
43
|
-
|
51
|
+
ids.each_slice(1000) do |some_ids|
|
52
|
+
execute <<-SQL
|
53
|
+
DELETE FROM #{name}
|
54
|
+
WHERE sphinx_internal_id IN (#{some_ids.join(', ')})
|
55
|
+
SQL
|
56
|
+
end
|
44
57
|
end
|
45
58
|
|
46
59
|
private
|
@@ -54,16 +67,4 @@ class ThinkingSphinx::Deletion
|
|
54
67
|
ThinkingSphinx::Configuration.instance
|
55
68
|
end
|
56
69
|
end
|
57
|
-
|
58
|
-
class PlainDeletion < ThinkingSphinx::Deletion
|
59
|
-
def perform
|
60
|
-
document_ids_for_keys.each_slice(1000) do |document_ids|
|
61
|
-
execute <<-SQL
|
62
|
-
UPDATE #{name}
|
63
|
-
SET sphinx_deleted = 1
|
64
|
-
WHERE id IN (#{document_ids.join(', ')})
|
65
|
-
SQL
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
70
|
end
|
@@ -34,11 +34,11 @@ class ThinkingSphinx::IndexSet
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def classes
|
37
|
-
options[:classes] ||
|
37
|
+
options[:classes] || instances.collect(&:class)
|
38
38
|
end
|
39
39
|
|
40
40
|
def classes_specified?
|
41
|
-
classes.any? || references_specified?
|
41
|
+
instances.any? || classes.any? || references_specified?
|
42
42
|
end
|
43
43
|
|
44
44
|
def classes_and_ancestors
|
@@ -68,6 +68,10 @@ class ThinkingSphinx::IndexSet
|
|
68
68
|
all_indices.select { |index| references.include? index.reference }
|
69
69
|
end
|
70
70
|
|
71
|
+
def instances
|
72
|
+
options[:instances] || []
|
73
|
+
end
|
74
|
+
|
71
75
|
def mti_classes
|
72
76
|
classes.reject { |klass|
|
73
77
|
klass.column_names.include?(klass.inheritance_column)
|
@@ -76,7 +80,7 @@ class ThinkingSphinx::IndexSet
|
|
76
80
|
|
77
81
|
def references
|
78
82
|
options[:references] || classes_and_ancestors.collect { |klass|
|
79
|
-
|
83
|
+
self.class.reference_name(klass)
|
80
84
|
}
|
81
85
|
end
|
82
86
|
|
@@ -133,7 +133,7 @@ class ThinkingSphinx::Middlewares::SphinxQL <
|
|
133
133
|
|
134
134
|
def indices_match_classes?
|
135
135
|
indices.collect(&:reference).uniq.sort == classes.collect { |klass|
|
136
|
-
|
136
|
+
configuration.index_set_class.reference_name(klass)
|
137
137
|
}.sort
|
138
138
|
end
|
139
139
|
|
@@ -7,7 +7,15 @@ class ThinkingSphinx::Railtie < Rails::Railtie
|
|
7
7
|
|
8
8
|
initializer 'thinking_sphinx.initialisation' do
|
9
9
|
ActiveSupport.on_load(:active_record) do
|
10
|
-
ActiveRecord::Base.
|
10
|
+
ActiveRecord::Base.include ThinkingSphinx::ActiveRecord::Base
|
11
|
+
end
|
12
|
+
|
13
|
+
if ActiveSupport::VERSION::MAJOR > 5
|
14
|
+
if Rails.application.config.autoloader == :zeitwerk
|
15
|
+
ActiveSupport::Dependencies.autoload_paths.delete(
|
16
|
+
Rails.root.join("app", "indices").to_s
|
17
|
+
)
|
18
|
+
end
|
11
19
|
end
|
12
20
|
end
|
13
21
|
|
@@ -8,6 +8,22 @@ module ThinkingSphinx::RealTime
|
|
8
8
|
def self.callback_for(reference, path = [], &block)
|
9
9
|
Callbacks::RealTimeCallbacks.new reference.to_sym, path, &block
|
10
10
|
end
|
11
|
+
|
12
|
+
def self.populator
|
13
|
+
@populator ||= ThinkingSphinx::RealTime::Populator
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.populator=(value)
|
17
|
+
@populator = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.processor
|
21
|
+
@processor ||= ThinkingSphinx::RealTime::Processor
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.processor=(value)
|
25
|
+
@processor = value
|
26
|
+
end
|
11
27
|
end
|
12
28
|
|
13
29
|
require 'thinking_sphinx/real_time/property'
|
@@ -16,6 +32,7 @@ require 'thinking_sphinx/real_time/field'
|
|
16
32
|
require 'thinking_sphinx/real_time/index'
|
17
33
|
require 'thinking_sphinx/real_time/interpreter'
|
18
34
|
require 'thinking_sphinx/real_time/populator'
|
35
|
+
require 'thinking_sphinx/real_time/processor'
|
19
36
|
require 'thinking_sphinx/real_time/transcribe_instance'
|
20
37
|
require 'thinking_sphinx/real_time/transcriber'
|
21
38
|
require 'thinking_sphinx/real_time/translator'
|
@@ -16,10 +16,14 @@ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def add_attribute(attribute)
|
19
|
+
@attributes.delete_if { |existing| existing.name == attribute.name }
|
20
|
+
|
19
21
|
@attributes << attribute
|
20
22
|
end
|
21
23
|
|
22
24
|
def add_field(field)
|
25
|
+
@fields.delete_if { |existing| existing.name == field.name }
|
26
|
+
|
23
27
|
@fields << field
|
24
28
|
end
|
25
29
|
|
@@ -5,16 +5,18 @@ class ThinkingSphinx::RealTime::Interpreter <
|
|
5
5
|
|
6
6
|
def has(*columns)
|
7
7
|
options = columns.extract_options!
|
8
|
-
|
8
|
+
|
9
|
+
columns.collect { |column|
|
9
10
|
::ThinkingSphinx::RealTime::Attribute.new column, options
|
10
|
-
}
|
11
|
+
}.each { |attribute| @index.add_attribute attribute }
|
11
12
|
end
|
12
13
|
|
13
14
|
def indexes(*columns)
|
14
15
|
options = columns.extract_options!
|
15
|
-
|
16
|
+
|
17
|
+
columns.collect { |column|
|
16
18
|
::ThinkingSphinx::RealTime::Field.new column, options
|
17
|
-
}
|
19
|
+
}.each { |field| @index.add_field field }
|
18
20
|
|
19
21
|
append_sortable_attributes columns, options if options[:sortable]
|
20
22
|
end
|
@@ -39,7 +41,7 @@ class ThinkingSphinx::RealTime::Interpreter <
|
|
39
41
|
def append_sortable_attributes(columns, options)
|
40
42
|
options = options.except(:sortable).merge(:type => :string)
|
41
43
|
|
42
|
-
|
44
|
+
columns.collect { |column|
|
43
45
|
aliased_name = options[:as]
|
44
46
|
aliased_name ||= column.__name.to_sym if column.respond_to?(:__name)
|
45
47
|
aliased_name ||= column
|
@@ -47,6 +49,6 @@ class ThinkingSphinx::RealTime::Interpreter <
|
|
47
49
|
options[:as] = "#{aliased_name}_sort".to_sym
|
48
50
|
|
49
51
|
::ThinkingSphinx::RealTime::Attribute.new column, options
|
50
|
-
}
|
52
|
+
}.each { |attribute| @index.add_attribute attribute }
|
51
53
|
end
|
52
54
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ThinkingSphinx::RealTime::Processor
|
4
|
+
def self.call(indices, &block)
|
5
|
+
new(indices).call(&block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(indices)
|
9
|
+
@indices = indices
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(&block)
|
13
|
+
subscribe_to_progress
|
14
|
+
|
15
|
+
indices.each do |index|
|
16
|
+
ThinkingSphinx::RealTime.populator.populate index
|
17
|
+
|
18
|
+
block.call
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :indices
|
25
|
+
|
26
|
+
def command
|
27
|
+
ThinkingSphinx::Commander.call(
|
28
|
+
command, configuration, options, stream
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def subscribe_to_progress
|
33
|
+
ThinkingSphinx::Subscribers::PopulatorSubscriber.
|
34
|
+
attach_to 'thinking_sphinx.real_time'
|
35
|
+
end
|
36
|
+
end
|
@@ -11,25 +11,8 @@ class ThinkingSphinx::RealTime::Transcriber
|
|
11
11
|
}
|
12
12
|
return unless items.present?
|
13
13
|
|
14
|
-
|
15
|
-
items
|
16
|
-
begin
|
17
|
-
values << ThinkingSphinx::RealTime::TranscribeInstance.call(
|
18
|
-
instance, index, properties
|
19
|
-
)
|
20
|
-
rescue ThinkingSphinx::TranscriptionError => error
|
21
|
-
instrument 'error', :error => error
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
insert = Riddle::Query::Insert.new index.name, columns, values
|
26
|
-
sphinxql = insert.replace!.to_sql
|
27
|
-
|
28
|
-
ThinkingSphinx::Logger.log :query, sphinxql do
|
29
|
-
ThinkingSphinx::Connection.take do |connection|
|
30
|
-
connection.execute sphinxql
|
31
|
-
end
|
32
|
-
end
|
14
|
+
delete_existing items
|
15
|
+
insert_replacements items
|
33
16
|
end
|
34
17
|
|
35
18
|
private
|
@@ -55,6 +38,27 @@ class ThinkingSphinx::RealTime::Transcriber
|
|
55
38
|
}
|
56
39
|
end
|
57
40
|
|
41
|
+
def delete_existing(instances)
|
42
|
+
ids = instances.collect(&index.primary_key.to_sym)
|
43
|
+
|
44
|
+
execute <<~SQL.strip
|
45
|
+
DELETE FROM #{@index.name} WHERE sphinx_internal_id IN (#{ids.join(', ')})
|
46
|
+
SQL
|
47
|
+
end
|
48
|
+
|
49
|
+
def execute(sphinxql)
|
50
|
+
ThinkingSphinx::Logger.log :query, sphinxql do
|
51
|
+
ThinkingSphinx::Connection.take do |connection|
|
52
|
+
connection.execute sphinxql
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def insert_replacements(instances)
|
58
|
+
insert = Riddle::Query::Insert.new index.name, columns, values(instances)
|
59
|
+
execute insert.replace!.to_sql
|
60
|
+
end
|
61
|
+
|
58
62
|
def instrument(message, options = {})
|
59
63
|
ActiveSupport::Notifications.instrument(
|
60
64
|
"#{message}.thinking_sphinx.real_time", options.merge(:index => index)
|
@@ -64,4 +68,16 @@ class ThinkingSphinx::RealTime::Transcriber
|
|
64
68
|
def properties
|
65
69
|
@properties ||= index.fields + index.attributes
|
66
70
|
end
|
71
|
+
|
72
|
+
def values(instances)
|
73
|
+
instances.each_with_object([]) do |instance, array|
|
74
|
+
begin
|
75
|
+
array << ThinkingSphinx::RealTime::TranscribeInstance.call(
|
76
|
+
instance, index, properties
|
77
|
+
)
|
78
|
+
rescue ThinkingSphinx::TranscriptionError => error
|
79
|
+
instrument 'error', :error => error
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
67
83
|
end
|