thinking-sphinx 4.3.1 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +150 -0
  3. data/.travis.yml +16 -21
  4. data/Appraisals +8 -17
  5. data/CHANGELOG.markdown +73 -0
  6. data/README.textile +14 -16
  7. data/bin/loadsphinx +30 -7
  8. data/lib/thinking_sphinx.rb +0 -2
  9. data/lib/thinking_sphinx/active_record.rb +1 -0
  10. data/lib/thinking_sphinx/active_record/association_proxy.rb +1 -2
  11. data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
  12. data/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb +1 -1
  13. data/lib/thinking_sphinx/active_record/base.rb +17 -6
  14. data/lib/thinking_sphinx/active_record/callbacks/association_delta_callbacks.rb +21 -0
  15. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -1
  16. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +3 -2
  17. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
  18. data/lib/thinking_sphinx/active_record/interpreter.rb +4 -4
  19. data/lib/thinking_sphinx/active_record/sql_source.rb +12 -0
  20. data/lib/thinking_sphinx/active_record/sql_source/template.rb +2 -2
  21. data/lib/thinking_sphinx/callbacks.rb +9 -0
  22. data/lib/thinking_sphinx/callbacks/appender.rb +59 -0
  23. data/lib/thinking_sphinx/commands/index_real_time.rb +1 -3
  24. data/lib/thinking_sphinx/configuration.rb +1 -1
  25. data/lib/thinking_sphinx/connection.rb +4 -0
  26. data/lib/thinking_sphinx/connection/client.rb +6 -1
  27. data/lib/thinking_sphinx/core/index.rb +5 -2
  28. data/lib/thinking_sphinx/deletion.rb +18 -17
  29. data/lib/thinking_sphinx/errors.rb +1 -1
  30. data/lib/thinking_sphinx/index_set.rb +7 -3
  31. data/lib/thinking_sphinx/middlewares/sphinxql.rb +1 -1
  32. data/lib/thinking_sphinx/railtie.rb +9 -1
  33. data/lib/thinking_sphinx/real_time.rb +17 -0
  34. data/lib/thinking_sphinx/real_time/index.rb +5 -3
  35. data/lib/thinking_sphinx/real_time/interpreter.rb +8 -6
  36. data/lib/thinking_sphinx/real_time/populator.rb +1 -1
  37. data/lib/thinking_sphinx/real_time/processor.rb +36 -0
  38. data/lib/thinking_sphinx/real_time/transcriber.rb +35 -19
  39. data/lib/thinking_sphinx/settings.rb +12 -9
  40. data/lib/thinking_sphinx/subscribers/populator_subscriber.rb +0 -4
  41. data/spec/acceptance/big_integers_spec.rb +1 -1
  42. data/spec/acceptance/geosearching_spec.rb +13 -3
  43. data/spec/acceptance/merging_spec.rb +1 -1
  44. data/spec/acceptance/real_time_updates_spec.rb +2 -2
  45. data/spec/acceptance/sql_deltas_spec.rb +15 -3
  46. data/spec/acceptance/support/sphinx_helpers.rb +4 -4
  47. data/spec/acceptance/suspended_deltas_spec.rb +3 -3
  48. data/spec/internal/app/indices/article_index.rb +0 -1
  49. data/spec/internal/app/indices/colour_index.rb +7 -0
  50. data/spec/internal/app/models/admin/person.rb +3 -1
  51. data/spec/internal/app/models/album.rb +3 -1
  52. data/spec/internal/app/models/animal.rb +1 -0
  53. data/spec/internal/app/models/article.rb +2 -0
  54. data/spec/internal/app/models/bird.rb +1 -0
  55. data/spec/internal/app/models/book.rb +2 -0
  56. data/spec/internal/app/models/car.rb +1 -1
  57. data/spec/internal/app/models/city.rb +2 -0
  58. data/spec/internal/app/models/colour.rb +2 -0
  59. data/spec/internal/app/models/product.rb +1 -1
  60. data/spec/internal/app/models/tee.rb +5 -0
  61. data/spec/internal/app/models/user.rb +2 -0
  62. data/spec/internal/config/database.yml +6 -1
  63. data/spec/internal/db/schema.rb +1 -0
  64. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +2 -1
  65. data/spec/thinking_sphinx/active_record/index_spec.rb +0 -12
  66. data/spec/thinking_sphinx/active_record/interpreter_spec.rb +15 -14
  67. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +41 -3
  68. data/spec/thinking_sphinx/configuration_spec.rb +1 -1
  69. data/spec/thinking_sphinx/connection/mri_spec.rb +49 -0
  70. data/spec/thinking_sphinx/deletion_spec.rb +5 -4
  71. data/spec/thinking_sphinx/index_set_spec.rb +28 -12
  72. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +2 -1
  73. data/spec/thinking_sphinx/real_time/index_spec.rb +38 -12
  74. data/spec/thinking_sphinx/real_time/interpreter_spec.rb +14 -14
  75. data/spec/thinking_sphinx/real_time/transcriber_spec.rb +9 -1
  76. data/thinking-sphinx.gemspec +3 -5
  77. metadata +17 -11
  78. data/spec/acceptance/connection_spec.rb +0 -25
@@ -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
 
@@ -61,12 +65,10 @@ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
61
65
 
62
66
  def collection_for(attribute)
63
67
  case attribute.type
64
- when :integer, :boolean
68
+ when :integer, :boolean, :timestamp
65
69
  attribute.multi? ? @rt_attr_multi : @rt_attr_uint
66
70
  when :string
67
71
  @rt_attr_string
68
- when :timestamp
69
- @rt_attr_timestamp
70
72
  when :float
71
73
  @rt_attr_float
72
74
  when :bigint
@@ -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
@@ -11,14 +11,15 @@ class ThinkingSphinx::Settings
11
11
  lemmatizer_base mysql_ssl_cert mysql_ssl_key mysql_ssl_ca
12
12
  ].freeze
13
13
  DEFAULTS = {
14
- "configuration_file" => "config/ENVIRONMENT.sphinx.conf",
15
- "indices_location" => "db/sphinx/ENVIRONMENT",
16
- "pid_file" => "log/ENVIRONMENT.sphinx.pid",
17
- "log" => "log/ENVIRONMENT.searchd.log",
18
- "query_log" => "log/ENVIRONMENT.searchd.query.log",
19
- "binlog_path" => "tmp/binlog/ENVIRONMENT",
20
- "workers" => "threads",
21
- "mysql_encoding" => "utf8"
14
+ "configuration_file" => "config/ENVIRONMENT.sphinx.conf",
15
+ "indices_location" => "db/sphinx/ENVIRONMENT",
16
+ "pid_file" => "log/ENVIRONMENT.sphinx.pid",
17
+ "log" => "log/ENVIRONMENT.searchd.log",
18
+ "query_log" => "log/ENVIRONMENT.searchd.query.log",
19
+ "binlog_path" => "tmp/binlog/ENVIRONMENT",
20
+ "workers" => "threads",
21
+ "mysql_encoding" => "utf8",
22
+ "maximum_statement_length" => (2 ** 23) - 5
22
23
  }.freeze
23
24
 
24
25
  def self.call(configuration)
@@ -62,7 +63,9 @@ class ThinkingSphinx::Settings
62
63
 
63
64
  def defaults
64
65
  DEFAULTS.inject({}) do |hash, (key, value)|
65
- value = value.gsub("ENVIRONMENT", framework.environment)
66
+ if value.is_a?(String)
67
+ value = value.gsub("ENVIRONMENT", framework.environment)
68
+ end
66
69
 
67
70
  if FILE_KEYS.include?(key)
68
71
  hash[key] = absolute value
@@ -46,7 +46,3 @@ Error transcribing #{instance.class} #{instance.id}:
46
46
  delegate :output, :to => ThinkingSphinx
47
47
  delegate :puts, :print, :to => :output
48
48
  end
49
-
50
- ThinkingSphinx::Subscribers::PopulatorSubscriber.attach_to(
51
- 'thinking_sphinx.real_time'
52
- )
@@ -52,7 +52,7 @@ describe '64 bit document ids', :live => true do
52
52
  context 'with Real-Time' do
53
53
  it 'handles large 32 bit integers with an offset multiplier' do
54
54
  product = Product.create! :name => "Widget"
55
- product.update_attributes :id => 980190962
55
+ product.update :id => 980190962
56
56
  expect(
57
57
  Product.search('widget', :indices => ['product_core']).to_a
58
58
  ).to eq([product])
@@ -38,10 +38,20 @@ describe 'Searching by latitude and longitude', :live => true do
38
38
  expected = {:mysql => 250326.906250, :postgresql => 250331.234375}
39
39
  end
40
40
 
41
- if ActiveRecord::Base.configurations['test']['adapter'][/postgres/]
42
- expect(cities.first.geodist).to eq(expected[:postgresql])
41
+ adapter = nil
42
+
43
+ if ActiveRecord::VERSION::STRING.to_f > 6.0
44
+ adapter = ActiveRecord::Base.configurations.configs_for.first.adapter
45
+ elsif ActiveRecord::VERSION::STRING.to_f > 5.2
46
+ adapter = ActiveRecord::Base.configurations.configs_for.first.config["adapter"]
47
+ else
48
+ adapter = ActiveRecord::Base.configurations['test']['adapter']
49
+ end
50
+
51
+ if adapter[/postgres/]
52
+ expect(cities.first.geodist).to be_within(0.01).of(expected[:postgresql])
43
53
  else # mysql
44
- expect(cities.first.geodist).to eq(expected[:mysql])
54
+ expect(cities.first.geodist).to be_within(0.01).of(expected[:mysql])
45
55
  end
46
56
  end
47
57
 
@@ -34,7 +34,7 @@ describe "Merging deltas", :live => true do
34
34
  Book.search("Space", :indices => ["book_core"]).to_a
35
35
  ).to eq([race])
36
36
 
37
- race.reload.update_attributes :title => "The Hate Race"
37
+ race.reload.update :title => "The Hate Race"
38
38
  sleep 0.25
39
39
  expect(
40
40
  Book.search("Race", :indices => ["book_delta"]).to_a
@@ -11,7 +11,7 @@ describe 'Updates to records in real-time indices', :live => true do
11
11
 
12
12
  it "handles attributes for sortable fields accordingly" do
13
13
  product = Product.create! :name => 'Red Fish'
14
- product.update_attributes :name => 'Blue Fish'
14
+ product.update :name => 'Blue Fish'
15
15
 
16
16
  expect(Product.search('blue fish', :indices => ['product_core']).to_a).
17
17
  to eq([product])
@@ -22,7 +22,7 @@ describe 'Updates to records in real-time indices', :live => true do
22
22
 
23
23
  expect(Admin::Person.search('Death').to_a).to eq([person])
24
24
 
25
- person.update_attributes :name => 'Mort'
25
+ person.update :name => 'Mort'
26
26
 
27
27
  expect(Admin::Person.search('Death').to_a).to be_empty
28
28
  expect(Admin::Person.search('Mort').to_a).to eq([person])
@@ -25,7 +25,7 @@ describe 'SQL delta indexing', :live => true do
25
25
 
26
26
  expect(Book.search('Harry').to_a).to eq([book])
27
27
 
28
- book.reload.update_attributes(:author => 'Terry Pratchett')
28
+ book.reload.update(:author => 'Terry Pratchett')
29
29
  sleep 0.25
30
30
 
31
31
  expect(Book.search('Terry').to_a).to eq([book])
@@ -37,7 +37,7 @@ describe 'SQL delta indexing', :live => true do
37
37
 
38
38
  expect(Book.search('Harry').to_a).to eq([book])
39
39
 
40
- book.reload.update_attributes(:author => 'Terry Pratchett')
40
+ book.reload.update(:author => 'Terry Pratchett')
41
41
  sleep 0.25
42
42
 
43
43
  expect(Book.search('Harry')).to be_empty
@@ -49,7 +49,7 @@ describe 'SQL delta indexing', :live => true do
49
49
 
50
50
  expect(Album.search('Whitloms').to_a).to eq([album])
51
51
 
52
- album.reload.update_attributes(:artist => 'The Whitlams')
52
+ album.reload.update(:artist => 'The Whitlams')
53
53
  sleep 0.25
54
54
 
55
55
  expect(Book.search('Whitloms')).to be_empty
@@ -63,4 +63,16 @@ describe 'SQL delta indexing', :live => true do
63
63
 
64
64
  expect(Book.search('Gaiman').to_a).to eq([book])
65
65
  end
66
+
67
+ it "updates associated models" do
68
+ colour = Colour.create(:name => 'green')
69
+ sleep 0.25
70
+
71
+ expect(Colour.search('green').to_a).to eq([colour])
72
+
73
+ tee = colour.tees.create
74
+ sleep 0.25
75
+
76
+ expect(Colour.search(:with => {:tee_ids => tee.id}).to_a).to eq([colour])
77
+ end
66
78
  end
@@ -6,22 +6,22 @@ module SphinxHelpers
6
6
  end
7
7
 
8
8
  def index(*indices)
9
- sleep 0.5 if ENV['TRAVIS']
9
+ sleep 0.5 if ENV['CI']
10
10
 
11
11
  yield if block_given?
12
12
 
13
13
  sphinx.index *indices
14
14
  sleep 0.25
15
- sleep 0.5 if ENV['TRAVIS']
15
+ sleep 0.5 if ENV['CI']
16
16
  end
17
17
 
18
18
  def merge
19
- sleep 0.5 if ENV['TRAVIS']
19
+ sleep 0.5 if ENV['CI']
20
20
  sleep 0.5
21
21
 
22
22
  sphinx.merge
23
23
  sleep 1.5
24
- sleep 0.5 if ENV['TRAVIS']
24
+ sleep 0.5 if ENV['CI']
25
25
  end
26
26
  end
27
27
 
@@ -10,7 +10,7 @@ describe 'Suspend deltas for a given action', :live => true do
10
10
  expect(Book.search('Harry').to_a).to eq([book])
11
11
 
12
12
  ThinkingSphinx::Deltas.suspend :book do
13
- book.reload.update_attributes(:author => 'Terry Pratchett')
13
+ book.reload.update(:author => 'Terry Pratchett')
14
14
  sleep 0.25
15
15
 
16
16
  expect(Book.search('Terry').to_a).to eq([])
@@ -27,7 +27,7 @@ describe 'Suspend deltas for a given action', :live => true do
27
27
  expect(Book.search('Harry').to_a).to eq([book])
28
28
 
29
29
  ThinkingSphinx::Deltas.suspend :book do
30
- book.reload.update_attributes(:author => 'Terry Pratchett')
30
+ book.reload.update(:author => 'Terry Pratchett')
31
31
  sleep 0.25
32
32
 
33
33
  expect(Book.search('Terry').to_a).to eq([])
@@ -44,7 +44,7 @@ describe 'Suspend deltas for a given action', :live => true do
44
44
  expect(Book.search('Harry').to_a).to eq([book])
45
45
 
46
46
  ThinkingSphinx::Deltas.suspend_and_update :book do
47
- book.reload.update_attributes(:author => 'Terry Pratchett')
47
+ book.reload.update(:author => 'Terry Pratchett')
48
48
  sleep 0.25
49
49
 
50
50
  expect(Book.search('Terry').to_a).to eq([])