thinking-sphinx 4.4.1 → 5.2.1

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.
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
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Animal < ActiveRecord::Base
4
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
4
5
  end
@@ -4,4 +4,6 @@ class Article < ActiveRecord::Base
4
4
  belongs_to :user
5
5
  has_many :taggings
6
6
  has_many :tags, :through => :taggings
7
+
8
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql, :updates])
7
9
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Bird < Animal
4
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
4
5
  end
@@ -5,6 +5,8 @@ class Book < ActiveRecord::Base
5
5
 
6
6
  has_and_belongs_to_many :genres
7
7
 
8
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql, :deltas])
9
+
8
10
  sphinx_scope(:by_query) { |query| query }
9
11
  sphinx_scope(:by_year) do |year|
10
12
  {:with => {:year => year}}
@@ -3,5 +3,5 @@
3
3
  class Car < ActiveRecord::Base
4
4
  belongs_to :manufacturer
5
5
 
6
- after_save ThinkingSphinx::RealTime.callback_for(:car)
6
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:real_time])
7
7
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class City < ActiveRecord::Base
4
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
5
+
4
6
  scope :ordered, lambda { order(:name) }
5
7
  end
@@ -2,4 +2,6 @@
2
2
 
3
3
  class Colour < ActiveRecord::Base
4
4
  has_many :tees
5
+
6
+ ThinkingSphinx::Callbacks.append(self, behaviours: [:sql, :deltas])
5
7
  end
@@ -4,5 +4,5 @@ class Product < ActiveRecord::Base
4
4
  has_many :categorisations
5
5
  has_many :categories, :through => :categorisations
6
6
 
7
- after_save ThinkingSphinx::RealTime.callback_for(:product)
7
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:real_time])
8
8
  end
@@ -2,4 +2,9 @@
2
2
 
3
3
  class Tee < ActiveRecord::Base
4
4
  belongs_to :colour
5
+
6
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
7
+ ThinkingSphinx::Callbacks.append(
8
+ self, behaviours: [:sql, :deltas], :path => [:colour]
9
+ )
5
10
  end
@@ -3,6 +3,8 @@
3
3
  class User < ActiveRecord::Base
4
4
  has_many :articles
5
5
 
6
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
7
+
6
8
  default_scope { order(:id) }
7
9
  scope :recent, lambda { where('created_at > ?', 1.week.ago) }
8
10
  end
@@ -1,6 +1,11 @@
1
1
  test:
2
2
  adapter: <%= ENV['DATABASE'] || 'mysql2' %>
3
3
  database: thinking_sphinx
4
- username: <%= ENV['DATABASE'] == 'postgresql' ? ENV['USER'] : 'root' %>
4
+ username: <%= ENV['DATABASE'] == 'postgresql' ? 'postgres' : 'root' %>
5
+ <% if ENV["CI"] %>
6
+ password: thinking_sphinx
7
+ host: 127.0.0.1
8
+ port: <%= ENV['DATABASE'] == 'postgresql' ? 5432 : 3306 %>
9
+ <% end %>
5
10
  min_messages: warning
6
11
  encoding: utf8
@@ -68,6 +68,7 @@ ActiveRecord::Schema.define do
68
68
 
69
69
  create_table(:colours, :force => true) do |t|
70
70
  t.string :name
71
+ t.boolean :delta, :null => false, :default => true
71
72
  t.timestamps null: false
72
73
  end
73
74
 
@@ -19,12 +19,13 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
19
19
  let(:klass) { double(:name => 'Article') }
20
20
  let(:configuration) { double('configuration',
21
21
  :settings => {'attribute_updates' => true},
22
- :indices_for_references => [index]) }
22
+ :indices_for_references => [index], :index_set_class => set_class) }
23
23
  let(:connection) { double('connection', :execute => '') }
24
24
  let(:index) { double 'index', :name => 'article_core',
25
25
  :sources => [source], :document_id_for_key => 3, :distributed? => false,
26
26
  :type => 'plain', :primary_key => :id}
27
27
  let(:source) { double('source', :attributes => []) }
28
+ let(:set_class) { double(:reference_name => :article) }
28
29
 
29
30
  before :each do
30
31
  stub_const 'ThinkingSphinx::Configuration',
@@ -94,18 +94,6 @@ describe ThinkingSphinx::ActiveRecord::Index do
94
94
  end
95
95
  end
96
96
 
97
- describe '#docinfo' do
98
- it "defaults to extern" do
99
- expect(index.docinfo).to eq(:extern)
100
- end
101
-
102
- it "can be disabled" do
103
- config.settings["skip_docinfo"] = true
104
-
105
- expect(index.docinfo).to be_nil
106
- end
107
- end
108
-
109
97
  describe '#document_id_for_key' do
110
98
  it "calculates the document id based on offset and number of indices" do
111
99
  allow(config).to receive_message_chain(:indices, :count).and_return(5)
@@ -15,8 +15,13 @@ describe ThinkingSphinx::ActiveRecord::Interpreter do
15
15
  let(:block) { Proc.new { } }
16
16
 
17
17
  before :each do
18
- allow(ThinkingSphinx::ActiveRecord::SQLSource).to receive_messages :new => source
19
- allow(source).to receive_messages :model => model
18
+ allow(ThinkingSphinx::ActiveRecord::SQLSource).to receive_messages(
19
+ :new => source
20
+ )
21
+
22
+ allow(source).to receive_messages(
23
+ :model => model, :add_attribute => nil, :add_field => nil
24
+ )
20
25
  end
21
26
 
22
27
  describe '.translate!' do
@@ -94,17 +99,15 @@ describe ThinkingSphinx::ActiveRecord::Interpreter do
94
99
  end
95
100
 
96
101
  it "adds an attribute to the source" do
97
- instance.has column
102
+ expect(source).to receive(:add_attribute).with(attribute)
98
103
 
99
- expect(source.attributes).to include(attribute)
104
+ instance.has column
100
105
  end
101
106
 
102
107
  it "adds multiple attributes when passed multiple columns" do
103
- instance.has column, column
108
+ expect(source).to receive(:add_attribute).with(attribute).twice
104
109
 
105
- expect(source.attributes.select { |saved_attribute|
106
- saved_attribute == attribute
107
- }.length).to eq(2)
110
+ instance.has column, column
108
111
  end
109
112
  end
110
113
 
@@ -144,17 +147,15 @@ describe ThinkingSphinx::ActiveRecord::Interpreter do
144
147
  end
145
148
 
146
149
  it "adds a field to the source" do
147
- instance.indexes column
150
+ expect(source).to receive(:add_field).with(field)
148
151
 
149
- expect(source.fields).to include(field)
152
+ instance.indexes column
150
153
  end
151
154
 
152
155
  it "adds multiple fields when passed multiple columns" do
153
- instance.indexes column, column
156
+ expect(source).to receive(:add_field).with(field).twice
154
157
 
155
- expect(source.fields.select { |saved_field|
156
- saved_field == field
157
- }.length).to eq(2)
158
+ instance.indexes column, column
158
159
  end
159
160
  end
160
161
 
@@ -30,6 +30,44 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
30
30
  end
31
31
  end
32
32
 
33
+ describe '#add_attribute' do
34
+ let(:attribute) { double('attribute', name: 'my_attribute') }
35
+
36
+ it "appends attributes to the collection" do
37
+ source.add_attribute attribute
38
+
39
+ expect(source.attributes.collect(&:name)).to include('my_attribute')
40
+ end
41
+
42
+ it "replaces attributes with the same name" do
43
+ source.add_attribute double('attribute', name: 'my_attribute')
44
+ source.add_attribute attribute
45
+
46
+ matching = source.attributes.select { |attr| attr.name == attribute.name }
47
+
48
+ expect(matching).to eq([attribute])
49
+ end
50
+ end
51
+
52
+ describe '#add_field' do
53
+ let(:field) { double('field', name: 'my_field') }
54
+
55
+ it "appends fields to the collection" do
56
+ source.add_field field
57
+
58
+ expect(source.fields.collect(&:name)).to include('my_field')
59
+ end
60
+
61
+ it "replaces fields with the same name" do
62
+ source.add_field double('field', name: 'my_field')
63
+ source.add_field field
64
+
65
+ matching = source.fields.select { |fld| fld.name == field.name }
66
+
67
+ expect(matching).to eq([field])
68
+ end
69
+ end
70
+
33
71
  describe '#attributes' do
34
72
  it "has the internal id attribute by default" do
35
73
  expect(source.attributes.collect(&:name)).to include('sphinx_internal_id')
@@ -287,14 +325,14 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
287
325
  expect(source.sql_attr_string).to include('name')
288
326
  end
289
327
 
290
- it "adds timestamp attributes to sql_attr_timestamp" do
328
+ it "adds timestamp attributes to sql_attr_uint" do
291
329
  source.attributes << double('attribute')
292
330
  allow(presenter).to receive_messages :declaration => 'created_at',
293
- :collection_type => :timestamp
331
+ :collection_type => :uint
294
332
 
295
333
  source.render
296
334
 
297
- expect(source.sql_attr_timestamp).to include('created_at')
335
+ expect(source.sql_attr_uint).to include('created_at')
298
336
  end
299
337
 
300
338
  it "adds float attributes to sql_attr_float" do
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe ThinkingSphinx::Connection::MRI do
4
+ subject { described_class.new :host => "127.0.0.1", :port => 9306 }
5
+
6
+ let(:client) { double :client, :query => "result", :next_result => false }
7
+
8
+ before :each do
9
+ allow(Mysql2::Client).to receive(:new).and_return(client)
10
+ end
11
+
12
+ after :each do
13
+ ThinkingSphinx::Configuration.reset
14
+ end
15
+
16
+ describe "#execute" do
17
+ it "sends the query to the client" do
18
+ subject.execute "SELECT QUERY"
19
+
20
+ expect(client).to have_received(:query).with("SELECT QUERY")
21
+ end
22
+
23
+ it "returns a result" do
24
+ expect(subject.execute("SELECT QUERY")).to eq("result")
25
+ end
26
+
27
+ context "with long queries" do
28
+ let(:maximum) { (2 ** 23) - 5 }
29
+ let(:query) { String.new "SELECT * FROM book_core WHERE MATCH('')" }
30
+ let(:difference) { maximum - query.length }
31
+
32
+ it 'does not allow overly long queries' do
33
+ expect {
34
+ subject.execute(query.insert(-3, 'a' * (difference + 5)))
35
+ }.to raise_error(ThinkingSphinx::QueryLengthError)
36
+ end
37
+
38
+ it 'does not allow queries longer than specified in the settings' do
39
+ ThinkingSphinx::Configuration.reset
40
+
41
+ write_configuration('maximum_statement_length' => maximum - 5)
42
+
43
+ expect {
44
+ subject.execute(query.insert(-3, 'a' * (difference)))
45
+ }.to raise_error(ThinkingSphinx::QueryLengthError)
46
+ end
47
+ end
48
+ end
49
+ end if RUBY_PLATFORM != 'java'
@@ -6,7 +6,7 @@ describe ThinkingSphinx::Deletion do
6
6
  describe '.perform' do
7
7
  let(:connection) { double('connection', :execute => nil) }
8
8
  let(:index) { double('index', :name => 'foo_core',
9
- :document_id_for_key => 14, :type => 'plain', :distributed? => false) }
9
+ :type => 'plain', :distributed? => false) }
10
10
 
11
11
  before :each do
12
12
  allow(ThinkingSphinx::Connection).to receive(:take).and_yield(connection)
@@ -15,8 +15,9 @@ describe ThinkingSphinx::Deletion do
15
15
 
16
16
  context 'index is SQL-backed' do
17
17
  it "updates the deleted flag to false" do
18
- expect(connection).to receive(:execute).
19
- with('UPDATE foo_core SET sphinx_deleted = 1 WHERE id IN (14)')
18
+ expect(connection).to receive(:execute).with(
19
+ 'UPDATE foo_core SET sphinx_deleted = 1 WHERE sphinx_internal_id IN (7)'
20
+ )
20
21
 
21
22
  ThinkingSphinx::Deletion.perform index, 7
22
23
  end
@@ -38,7 +39,7 @@ describe ThinkingSphinx::Deletion do
38
39
 
39
40
  it "deletes the record to false" do
40
41
  expect(connection).to receive(:execute).
41
- with('DELETE FROM foo_core WHERE id = 14')
42
+ with('DELETE FROM foo_core WHERE sphinx_internal_id IN (7)')
42
43
 
43
44
  ThinkingSphinx::Deletion.perform index, 7
44
45
  end
@@ -30,6 +30,16 @@ describe ThinkingSphinx::IndexSet do
30
30
  end
31
31
 
32
32
  describe '#to_a' do
33
+ let(:article_index) do
34
+ double(:reference => :article, :distributed? => false)
35
+ end
36
+ let(:opinion_article_index) do
37
+ double(:reference => :opinion_article, :distributed? => false)
38
+ end
39
+ let(:page_index) do
40
+ double(:reference => :page, :distributed? => false)
41
+ end
42
+
33
43
  it "ensures the indices are loaded" do
34
44
  expect(configuration).to receive(:preload_indices)
35
45
 
@@ -50,21 +60,29 @@ describe ThinkingSphinx::IndexSet do
50
60
 
51
61
  it "uses indices for the given classes" do
52
62
  configuration.indices.replace [
53
- double(:reference => :article, :distributed? => false),
54
- double(:reference => :opinion_article, :distributed? => false),
55
- double(:reference => :page, :distributed? => false)
63
+ article_index, opinion_article_index, page_index
56
64
  ]
57
65
 
58
66
  options[:classes] = [class_double('Article', :column_names => [])]
59
67
 
60
- expect(set.to_a.length).to eq(1)
68
+ expect(set.to_a).to eq([article_index])
69
+ end
70
+
71
+ it "uses indices for the given instance's class" do
72
+ configuration.indices.replace [
73
+ article_index, opinion_article_index, page_index
74
+ ]
75
+
76
+ instance_class = class_double('Article', :column_names => [])
77
+
78
+ options[:instances] = [double(:instance, :class => instance_class)]
79
+
80
+ expect(set.to_a).to eq([article_index])
61
81
  end
62
82
 
63
83
  it "requests indices for any STI superclasses" do
64
84
  configuration.indices.replace [
65
- double(:reference => :article, :distributed? => false),
66
- double(:reference => :opinion_article, :distributed? => false),
67
- double(:reference => :page, :distributed? => false)
85
+ article_index, opinion_article_index, page_index
68
86
  ]
69
87
 
70
88
  article = class_double('Article', :column_names => [:type])
@@ -73,14 +91,12 @@ describe ThinkingSphinx::IndexSet do
73
91
 
74
92
  options[:classes] = [opinion]
75
93
 
76
- expect(set.to_a.length).to eq(2)
94
+ expect(set.to_a).to eq([article_index, opinion_article_index])
77
95
  end
78
96
 
79
97
  it "does not use MTI superclasses" do
80
98
  configuration.indices.replace [
81
- double(:reference => :article, :distributed? => false),
82
- double(:reference => :opinion_article, :distributed? => false),
83
- double(:reference => :page, :distributed? => false)
99
+ article_index, opinion_article_index, page_index
84
100
  ]
85
101
 
86
102
  article = class_double('Article', :column_names => [])
@@ -88,7 +104,7 @@ describe ThinkingSphinx::IndexSet do
88
104
 
89
105
  options[:classes] = [opinion]
90
106
 
91
- expect(set.to_a.length).to eq(1)
107
+ expect(set.to_a).to eq([opinion_article_index])
92
108
  end
93
109
 
94
110
  it "uses named indices if names are provided" do
@@ -31,7 +31,7 @@ describe ThinkingSphinx::Middlewares::SphinxQL do
31
31
  let(:query) { double('query') }
32
32
  let(:configuration) { double('configuration', :settings => {},
33
33
  index_set_class: set_class) }
34
- let(:set_class) { double(:new => index_set) }
34
+ let(:set_class) { double(:new => index_set, :reference_name => :article) }
35
35
 
36
36
  before :each do
37
37
  stub_const 'Riddle::Query::Select', double(:new => sphinx_sql)
@@ -115,6 +115,7 @@ describe ThinkingSphinx::Middlewares::SphinxQL do
115
115
  model = double('model', :connection => double,
116
116
  :ancestors => [ActiveRecord::Base], :name => 'Animal')
117
117
  allow(index_set.first).to receive_messages :reference => :animal
118
+ allow(set_class).to receive_messages(:reference_name => :animal)
118
119
 
119
120
  search.options[:classes] = [model]
120
121