thinking-sphinx 4.4.1 → 5.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +152 -0
  3. data/.travis.yml +12 -21
  4. data/Appraisals +10 -19
  5. data/CHANGELOG.markdown +62 -0
  6. data/README.textile +15 -17
  7. data/bin/loadsphinx +17 -4
  8. data/lib/thinking_sphinx.rb +5 -4
  9. data/lib/thinking_sphinx/active_record.rb +1 -0
  10. data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
  11. data/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb +1 -1
  12. data/lib/thinking_sphinx/active_record/base.rb +2 -6
  13. data/lib/thinking_sphinx/active_record/callbacks/association_delta_callbacks.rb +21 -0
  14. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -1
  15. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +3 -2
  16. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
  17. data/lib/thinking_sphinx/active_record/interpreter.rb +4 -4
  18. data/lib/thinking_sphinx/active_record/sql_source.rb +12 -0
  19. data/lib/thinking_sphinx/active_record/sql_source/template.rb +2 -2
  20. data/lib/thinking_sphinx/callbacks.rb +9 -0
  21. data/lib/thinking_sphinx/callbacks/appender.rb +59 -0
  22. data/lib/thinking_sphinx/connection.rb +4 -0
  23. data/lib/thinking_sphinx/connection/client.rb +6 -1
  24. data/lib/thinking_sphinx/core/index.rb +1 -2
  25. data/lib/thinking_sphinx/deletion.rb +18 -17
  26. data/lib/thinking_sphinx/errors.rb +1 -1
  27. data/lib/thinking_sphinx/index_set.rb +7 -3
  28. data/lib/thinking_sphinx/middlewares/sphinxql.rb +1 -1
  29. data/lib/thinking_sphinx/railtie.rb +9 -4
  30. data/lib/thinking_sphinx/real_time/index.rb +5 -3
  31. data/lib/thinking_sphinx/real_time/index/template.rb +12 -0
  32. data/lib/thinking_sphinx/real_time/interpreter.rb +8 -6
  33. data/lib/thinking_sphinx/real_time/populator.rb +4 -1
  34. data/lib/thinking_sphinx/real_time/transcriber.rb +41 -19
  35. data/lib/thinking_sphinx/real_time/translator.rb +1 -0
  36. data/lib/thinking_sphinx/settings.rb +13 -9
  37. data/spec/acceptance/big_integers_spec.rb +1 -1
  38. data/spec/acceptance/geosearching_spec.rb +13 -3
  39. data/spec/acceptance/merging_spec.rb +1 -1
  40. data/spec/acceptance/real_time_updates_spec.rb +2 -2
  41. data/spec/acceptance/sql_deltas_spec.rb +15 -3
  42. data/spec/acceptance/support/sphinx_helpers.rb +4 -4
  43. data/spec/acceptance/suspended_deltas_spec.rb +3 -3
  44. data/spec/internal/app/indices/article_index.rb +0 -1
  45. data/spec/internal/app/indices/colour_index.rb +7 -0
  46. data/spec/internal/app/models/admin/person.rb +3 -1
  47. data/spec/internal/app/models/album.rb +3 -1
  48. data/spec/internal/app/models/animal.rb +1 -0
  49. data/spec/internal/app/models/article.rb +2 -0
  50. data/spec/internal/app/models/bird.rb +1 -0
  51. data/spec/internal/app/models/book.rb +2 -0
  52. data/spec/internal/app/models/car.rb +1 -1
  53. data/spec/internal/app/models/city.rb +2 -0
  54. data/spec/internal/app/models/colour.rb +2 -0
  55. data/spec/internal/app/models/product.rb +1 -1
  56. data/spec/internal/app/models/tee.rb +5 -0
  57. data/spec/internal/app/models/user.rb +2 -0
  58. data/spec/internal/config/database.yml +6 -1
  59. data/spec/internal/db/schema.rb +1 -0
  60. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +2 -1
  61. data/spec/thinking_sphinx/active_record/index_spec.rb +0 -12
  62. data/spec/thinking_sphinx/active_record/interpreter_spec.rb +15 -14
  63. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +41 -3
  64. data/spec/thinking_sphinx/connection/mri_spec.rb +49 -0
  65. data/spec/thinking_sphinx/deletion_spec.rb +5 -4
  66. data/spec/thinking_sphinx/index_set_spec.rb +28 -12
  67. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +2 -1
  68. data/spec/thinking_sphinx/real_time/index_spec.rb +51 -13
  69. data/spec/thinking_sphinx/real_time/interpreter_spec.rb +14 -14
  70. data/spec/thinking_sphinx/real_time/transcriber_spec.rb +9 -1
  71. data/thinking-sphinx.gemspec +3 -3
  72. metadata +17 -12
  73. data/spec/acceptance/connection_spec.rb +0 -25
@@ -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
@@ -13,6 +13,10 @@ class ThinkingSphinx::RealTime::Index::Template
13
13
  add_attribute primary_key, :sphinx_internal_id, :bigint
14
14
  add_attribute class_column, :sphinx_internal_class, :string, :facet => true
15
15
  add_attribute 0, :sphinx_deleted, :integer
16
+
17
+ if tidying?
18
+ add_attribute -> (_) { Time.current.to_i }, :sphinx_updated_at, :timestamp
19
+ end
16
20
  end
17
21
 
18
22
  private
@@ -34,7 +38,15 @@ class ThinkingSphinx::RealTime::Index::Template
34
38
  [:class, :name]
35
39
  end
36
40
 
41
+ def config
42
+ ThinkingSphinx::Configuration.instance
43
+ end
44
+
37
45
  def primary_key
38
46
  index.primary_key.to_sym
39
47
  end
48
+
49
+ def tidying?
50
+ config.settings["real_time_tidy"]
51
+ end
40
52
  end
@@ -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
@@ -7,6 +7,7 @@ class ThinkingSphinx::RealTime::Populator
7
7
 
8
8
  def initialize(index)
9
9
  @index = index
10
+ @started_at = Time.current
10
11
  end
11
12
 
12
13
  def populate
@@ -17,12 +18,14 @@ class ThinkingSphinx::RealTime::Populator
17
18
  instrument 'populated', :instances => instances
18
19
  end
19
20
 
21
+ transcriber.clear_before(started_at) if configuration.settings["real_time_tidy"]
22
+
20
23
  instrument 'finish_populating'
21
24
  end
22
25
 
23
26
  private
24
27
 
25
- attr_reader :index
28
+ attr_reader :index, :started_at
26
29
 
27
30
  delegate :controller, :batch_size, :to => :configuration
28
31
  delegate :scope, :to => :index
@@ -5,31 +5,20 @@ class ThinkingSphinx::RealTime::Transcriber
5
5
  @index = index
6
6
  end
7
7
 
8
+ def clear_before(time)
9
+ execute <<~SQL.strip
10
+ DELETE FROM #{@index.name} WHERE sphinx_updated_at < #{time.to_i}
11
+ SQL
12
+ end
13
+
8
14
  def copy(*instances)
9
15
  items = instances.select { |instance|
10
16
  instance.persisted? && copy?(instance)
11
17
  }
12
18
  return unless items.present?
13
19
 
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
20
+ delete_existing items
21
+ insert_replacements items
33
22
  end
34
23
 
35
24
  private
@@ -55,6 +44,27 @@ class ThinkingSphinx::RealTime::Transcriber
55
44
  }
56
45
  end
57
46
 
47
+ def delete_existing(instances)
48
+ ids = instances.collect(&index.primary_key.to_sym)
49
+
50
+ execute <<~SQL.strip
51
+ DELETE FROM #{@index.name} WHERE sphinx_internal_id IN (#{ids.join(', ')})
52
+ SQL
53
+ end
54
+
55
+ def execute(sphinxql)
56
+ ThinkingSphinx::Logger.log :query, sphinxql do
57
+ ThinkingSphinx::Connection.take do |connection|
58
+ connection.execute sphinxql
59
+ end
60
+ end
61
+ end
62
+
63
+ def insert_replacements(instances)
64
+ insert = Riddle::Query::Insert.new index.name, columns, values(instances)
65
+ execute insert.replace!.to_sql
66
+ end
67
+
58
68
  def instrument(message, options = {})
59
69
  ActiveSupport::Notifications.instrument(
60
70
  "#{message}.thinking_sphinx.real_time", options.merge(:index => index)
@@ -64,4 +74,16 @@ class ThinkingSphinx::RealTime::Transcriber
64
74
  def properties
65
75
  @properties ||= index.fields + index.attributes
66
76
  end
77
+
78
+ def values(instances)
79
+ instances.each_with_object([]) do |instance, array|
80
+ begin
81
+ array << ThinkingSphinx::RealTime::TranscribeInstance.call(
82
+ instance, index, properties
83
+ )
84
+ rescue ThinkingSphinx::TranscriptionError => error
85
+ instrument 'error', :error => error
86
+ end
87
+ end
88
+ end
67
89
  end
@@ -10,6 +10,7 @@ class ThinkingSphinx::RealTime::Translator
10
10
  end
11
11
 
12
12
  def call
13
+ return name.call(object) if name.is_a?(Proc)
13
14
  return name unless name.is_a?(Symbol)
14
15
  return result unless result.is_a?(String)
15
16
 
@@ -11,14 +11,16 @@ 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,
23
+ "real_time_tidy" => false
22
24
  }.freeze
23
25
 
24
26
  def self.call(configuration)
@@ -62,7 +64,9 @@ class ThinkingSphinx::Settings
62
64
 
63
65
  def defaults
64
66
  DEFAULTS.inject({}) do |hash, (key, value)|
65
- value = value.gsub("ENVIRONMENT", framework.environment)
67
+ if value.is_a?(String)
68
+ value = value.gsub("ENVIRONMENT", framework.environment)
69
+ end
66
70
 
67
71
  if FILE_KEYS.include?(key)
68
72
  hash[key] = absolute value
@@ -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([])
@@ -10,7 +10,6 @@ ThinkingSphinx::Index.define :article, :with => :active_record do
10
10
  has taggings.created_at, :as => :taggings_at, :type => :timestamp
11
11
 
12
12
  set_property :min_infix_len => 4
13
- set_property :enable_star => true
14
13
  end
15
14
 
16
15
  ThinkingSphinx::Index.define :article, :with => :active_record,
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ ThinkingSphinx::Index.define :colour, :with => :active_record, :delta => true do
4
+ indexes name
5
+
6
+ has tees.id, :as => :tee_ids
7
+ end
@@ -3,5 +3,7 @@
3
3
  class Admin::Person < ActiveRecord::Base
4
4
  self.table_name = 'admin_people'
5
5
 
6
- after_save ThinkingSphinx::RealTime.callback_for('admin/person')
6
+ ThinkingSphinx::Callbacks.append(
7
+ self, 'admin/person', :behaviours => [:sql, :real_time]
8
+ )
7
9
  end
@@ -6,7 +6,9 @@ class Album < ActiveRecord::Base
6
6
  before_validation :set_id, :on => :create
7
7
  before_validation :set_integer_id, :on => :create
8
8
 
9
- after_save ThinkingSphinx::RealTime.callback_for(:album)
9
+ ThinkingSphinx::Callbacks.append(
10
+ self, :behaviours => [:sql, :real_time, :deltas]
11
+ )
10
12
 
11
13
  validates :id, :presence => true, :uniqueness => true
12
14
  validates :integer_id, :presence => true, :uniqueness => true