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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +16 -21
  3. data/Appraisals +2 -17
  4. data/CHANGELOG.markdown +62 -1
  5. data/README.textile +14 -16
  6. data/bin/loadsphinx +20 -5
  7. data/lib/thinking_sphinx/active_record/association_proxy.rb +1 -2
  8. data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
  9. data/lib/thinking_sphinx/active_record/base.rb +17 -6
  10. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -1
  11. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +3 -2
  12. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
  13. data/lib/thinking_sphinx/active_record/interpreter.rb +4 -4
  14. data/lib/thinking_sphinx/active_record/sql_source.rb +12 -0
  15. data/lib/thinking_sphinx/active_record/sql_source/template.rb +2 -2
  16. data/lib/thinking_sphinx/callbacks.rb +9 -0
  17. data/lib/thinking_sphinx/callbacks/appender.rb +47 -0
  18. data/lib/thinking_sphinx/commands/index_real_time.rb +1 -3
  19. data/lib/thinking_sphinx/configuration.rb +9 -3
  20. data/lib/thinking_sphinx/core/index.rb +5 -2
  21. data/lib/thinking_sphinx/deletion.rb +18 -17
  22. data/lib/thinking_sphinx/index_set.rb +7 -3
  23. data/lib/thinking_sphinx/middlewares/sphinxql.rb +1 -1
  24. data/lib/thinking_sphinx/railtie.rb +9 -1
  25. data/lib/thinking_sphinx/real_time.rb +17 -0
  26. data/lib/thinking_sphinx/real_time/index.rb +4 -0
  27. data/lib/thinking_sphinx/real_time/interpreter.rb +8 -6
  28. data/lib/thinking_sphinx/real_time/populator.rb +1 -1
  29. data/lib/thinking_sphinx/real_time/processor.rb +36 -0
  30. data/lib/thinking_sphinx/real_time/transcriber.rb +35 -19
  31. data/lib/thinking_sphinx/subscribers/populator_subscriber.rb +0 -4
  32. data/spec/acceptance/big_integers_spec.rb +1 -1
  33. data/spec/acceptance/merging_spec.rb +1 -1
  34. data/spec/acceptance/real_time_updates_spec.rb +2 -2
  35. data/spec/acceptance/sql_deltas_spec.rb +3 -3
  36. data/spec/acceptance/suspended_deltas_spec.rb +3 -3
  37. data/spec/internal/app/models/admin/person.rb +3 -1
  38. data/spec/internal/app/models/album.rb +3 -1
  39. data/spec/internal/app/models/animal.rb +1 -0
  40. data/spec/internal/app/models/article.rb +2 -0
  41. data/spec/internal/app/models/bird.rb +1 -0
  42. data/spec/internal/app/models/book.rb +2 -0
  43. data/spec/internal/app/models/car.rb +1 -1
  44. data/spec/internal/app/models/city.rb +2 -0
  45. data/spec/internal/app/models/product.rb +1 -1
  46. data/spec/internal/app/models/tee.rb +2 -0
  47. data/spec/internal/app/models/user.rb +2 -0
  48. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +2 -1
  49. data/spec/thinking_sphinx/active_record/index_spec.rb +0 -12
  50. data/spec/thinking_sphinx/active_record/interpreter_spec.rb +15 -14
  51. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +38 -0
  52. data/spec/thinking_sphinx/configuration_spec.rb +17 -16
  53. data/spec/thinking_sphinx/deletion_spec.rb +5 -4
  54. data/spec/thinking_sphinx/index_set_spec.rb +28 -12
  55. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +2 -1
  56. data/spec/thinking_sphinx/real_time/index_spec.rb +38 -12
  57. data/spec/thinking_sphinx/real_time/interpreter_spec.rb +14 -14
  58. data/spec/thinking_sphinx/real_time/transcriber_spec.rb +9 -1
  59. data/thinking-sphinx.gemspec +3 -5
  60. 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].each do |index|
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 do |file|
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
- return unless model.table_exists?
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
- execute Riddle::Query::Delete.new(name, document_ids_for_keys).to_sql
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
- ThinkingSphinx::IndexSet.reference_name(klass)
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
- ThinkingSphinx::IndexSet.reference_name(klass)
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.send :include, ThinkingSphinx::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
- @index.attributes += columns.collect { |column|
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
- @index.fields += columns.collect { |column|
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
- @index.attributes += columns.collect { |column|
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
@@ -9,7 +9,7 @@ class ThinkingSphinx::RealTime::Populator
9
9
  @index = index
10
10
  end
11
11
 
12
- def populate(&block)
12
+ def populate
13
13
  instrument 'start_populating'
14
14
 
15
15
  scope.find_in_batches(:batch_size => batch_size) do |instances|
@@ -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
- values = []
15
- items.each do |instance|
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