thinking-sphinx 3.0.0 → 3.0.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 (52) hide show
  1. data/.travis.yml +6 -3
  2. data/HISTORY +18 -0
  3. data/README.textile +31 -8
  4. data/gemfiles/rails_3_1.gemfile +2 -2
  5. data/gemfiles/rails_3_2.gemfile +2 -2
  6. data/lib/thinking/sphinx.rb +1 -0
  7. data/lib/thinking_sphinx.rb +1 -0
  8. data/lib/thinking_sphinx/active_record.rb +2 -0
  9. data/lib/thinking_sphinx/active_record/association.rb +8 -0
  10. data/lib/thinking_sphinx/active_record/associations.rb +25 -5
  11. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +1 -5
  12. data/lib/thinking_sphinx/active_record/column.rb +12 -0
  13. data/lib/thinking_sphinx/active_record/database_adapters.rb +7 -0
  14. data/lib/thinking_sphinx/active_record/filtered_reflection.rb +49 -0
  15. data/lib/thinking_sphinx/active_record/interpreter.rb +6 -0
  16. data/lib/thinking_sphinx/active_record/polymorpher.rb +50 -0
  17. data/lib/thinking_sphinx/active_record/property.rb +6 -0
  18. data/lib/thinking_sphinx/active_record/property_query.rb +1 -1
  19. data/lib/thinking_sphinx/active_record/property_sql_presenter.rb +7 -1
  20. data/lib/thinking_sphinx/active_record/sql_builder.rb +7 -2
  21. data/lib/thinking_sphinx/active_record/sql_source.rb +5 -1
  22. data/lib/thinking_sphinx/capistrano.rb +64 -0
  23. data/lib/thinking_sphinx/configuration.rb +10 -1
  24. data/lib/thinking_sphinx/connection.rb +20 -0
  25. data/lib/thinking_sphinx/errors.rb +24 -0
  26. data/lib/thinking_sphinx/middlewares/sphinxql.rb +4 -1
  27. data/lib/thinking_sphinx/real_time/transcriber.rb +1 -1
  28. data/lib/thinking_sphinx/search/batch_inquirer.rb +3 -1
  29. data/lib/thinking_sphinx/search/glaze.rb +3 -3
  30. data/spec/acceptance/index_options_spec.rb +5 -0
  31. data/spec/acceptance/searching_on_fields_spec.rb +1 -0
  32. data/spec/acceptance/specifying_sql_spec.rb +107 -0
  33. data/spec/acceptance/sql_deltas_spec.rb +9 -0
  34. data/spec/acceptance/support/sphinx_controller.rb +1 -0
  35. data/spec/internal/app/models/event.rb +3 -0
  36. data/spec/internal/app/models/hardcover.rb +3 -0
  37. data/spec/internal/db/schema.rb +7 -1
  38. data/spec/thinking_sphinx/active_record/associations_spec.rb +2 -1
  39. data/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb +3 -3
  40. data/spec/thinking_sphinx/active_record/column_spec.rb +23 -0
  41. data/spec/thinking_sphinx/active_record/database_adapters_spec.rb +18 -0
  42. data/spec/thinking_sphinx/active_record/filtered_reflection_spec.rb +141 -0
  43. data/spec/thinking_sphinx/active_record/polymorpher_spec.rb +65 -0
  44. data/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb +28 -4
  45. data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +11 -1
  46. data/spec/thinking_sphinx/configuration_spec.rb +24 -0
  47. data/spec/thinking_sphinx/connection_spec.rb +82 -0
  48. data/spec/thinking_sphinx/errors_spec.rb +36 -0
  49. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +25 -0
  50. data/spec/thinking_sphinx/search/glaze_spec.rb +3 -0
  51. data/thinking-sphinx.gemspec +1 -1
  52. metadata +25 -3
@@ -16,7 +16,8 @@ describe ThinkingSphinx::ActiveRecord::Associations do
16
16
  double 'join',
17
17
  :join_type= => nil,
18
18
  :aliased_table_name => table_alias,
19
- :reflection => double('reflection')
19
+ :reflection => double('reflection'),
20
+ :conditions => []
20
21
  end
21
22
 
22
23
  def model_double(table_name = nil)
@@ -46,7 +46,7 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks do
46
46
  }
47
47
 
48
48
  before :each do
49
- config.stub :indices_for_references => [index]
49
+ ThinkingSphinx::IndexSet.stub :new => [index]
50
50
  end
51
51
 
52
52
  context 'without delta indices' do
@@ -72,7 +72,7 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks do
72
72
  before :each do
73
73
  ThinkingSphinx::Deltas.stub :suspended? => false
74
74
 
75
- config.stub :indices_for_references => [core_index, delta_index]
75
+ ThinkingSphinx::IndexSet.stub :new => [core_index, delta_index]
76
76
  end
77
77
 
78
78
  it "only indexes delta indices" do
@@ -127,7 +127,7 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks do
127
127
  }
128
128
 
129
129
  before :each do
130
- config.stub :indices_for_references => [index]
130
+ ThinkingSphinx::IndexSet.stub :new => [index]
131
131
  end
132
132
 
133
133
  it "sets delta to true if there are delta indices" do
@@ -8,6 +8,29 @@ describe ThinkingSphinx::ActiveRecord::Column do
8
8
  end
9
9
  end
10
10
 
11
+ describe '#__replace' do
12
+ let(:base) { [:a, :b] }
13
+ let(:replacements) { [[:a, :c], [:a, :d]] }
14
+
15
+ it "returns itself when it's a string column" do
16
+ column = ThinkingSphinx::ActiveRecord::Column.new('foo')
17
+ column.__replace(base, replacements).collect(&:__path).
18
+ should == [['foo']]
19
+ end
20
+
21
+ it "returns itself when the base of the stack does not match" do
22
+ column = ThinkingSphinx::ActiveRecord::Column.new(:b, :c)
23
+ column.__replace(base, replacements).collect(&:__path).
24
+ should == [[:b, :c]]
25
+ end
26
+
27
+ it "returns an array of new columns " do
28
+ column = ThinkingSphinx::ActiveRecord::Column.new(:a, :b, :e)
29
+ column.__replace(base, replacements).collect(&:__path).
30
+ should == [[:a, :c, :e], [:a, :d, :e]]
31
+ end
32
+ end
33
+
11
34
  describe '#__stack' do
12
35
  it "returns all but the top item" do
13
36
  column = ThinkingSphinx::ActiveRecord::Column.new(:users, :posts, :id)
@@ -89,6 +89,24 @@ describe ThinkingSphinx::ActiveRecord::DatabaseAdapters do
89
89
  adapter_type_for(model).should == :postgresql
90
90
  end
91
91
 
92
+ it "translates a JDBC adapter with MySQL connection string to MySQL" do
93
+ klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
94
+ connection.stub(:config => {:adapter => 'jdbc',
95
+ :url => 'jdbc:mysql://127.0.0.1:3306/sphinx'})
96
+
97
+ ThinkingSphinx::ActiveRecord::DatabaseAdapters.
98
+ adapter_type_for(model).should == :mysql
99
+ end
100
+
101
+ it "translates a JDBC adapter with PostgresSQL connection string to PostgresSQL" do
102
+ klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
103
+ connection.stub(:config => {:adapter => 'jdbc',
104
+ :url => 'jdbc:postgresql://127.0.0.1:3306/sphinx'})
105
+
106
+ ThinkingSphinx::ActiveRecord::DatabaseAdapters.
107
+ adapter_type_for(model).should == :postgresql
108
+ end
109
+
92
110
  it "returns other JDBC adapters without translation" do
93
111
  klass.stub(:name => 'ActiveRecord::ConnectionAdapters::JdbcAdapter')
94
112
  connection.stub(:config => {:adapter => 'jdbcmssql'})
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::ActiveRecord::FilteredReflection do
4
+ describe '.clone_with_filter' do
5
+ let(:reflection) { double('Reflection', :macro => :has_some,
6
+ :options => options, :active_record => double, :name => 'baz',
7
+ :foreign_type => :foo_type) }
8
+ let(:options) { {:polymorphic => true} }
9
+ let(:filtered_reflection) { double }
10
+
11
+ before :each do
12
+ ThinkingSphinx::ActiveRecord::FilteredReflection.stub(
13
+ :new => filtered_reflection
14
+ )
15
+
16
+ reflection.active_record.stub_chain(:connection, :quote_column_name).
17
+ and_return('"foo_type"')
18
+ end
19
+
20
+ it "uses the existing reflection's macro" do
21
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new).
22
+ with(:has_some, anything, anything, anything)
23
+
24
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
25
+ reflection, 'foo_bar', 'Bar'
26
+ )
27
+ end
28
+
29
+ it "uses the supplied name" do
30
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new).
31
+ with(anything, 'foo_bar', anything, anything)
32
+
33
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
34
+ reflection, 'foo_bar', 'Bar'
35
+ )
36
+ end
37
+
38
+ it "uses the existing reflection's parent" do
39
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new).
40
+ with(anything, anything, anything, reflection.active_record)
41
+
42
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
43
+ reflection, 'foo_bar', 'Bar'
44
+ )
45
+ end
46
+
47
+ it "removes the polymorphic setting from the options" do
48
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new) do |macro, name, options, parent|
49
+ options[:polymorphic].should be_nil
50
+ end
51
+
52
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
53
+ reflection, 'foo_bar', 'Bar'
54
+ )
55
+ end
56
+
57
+ it "adds the class name option" do
58
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new) do |macro, name, options, parent|
59
+ options[:class_name].should == 'Bar'
60
+ end
61
+
62
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
63
+ reflection, 'foo_bar', 'Bar'
64
+ )
65
+ end
66
+
67
+ it "sets the foreign key if necessary" do
68
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new) do |macro, name, options, parent|
69
+ options[:foreign_key].should == 'baz_id'
70
+ end
71
+
72
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
73
+ reflection, 'foo_bar', 'Bar'
74
+ )
75
+ end
76
+
77
+ it "respects supplied foreign keys" do
78
+ options[:foreign_key] = 'qux_id'
79
+
80
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new) do |macro, name, options, parent|
81
+ options[:foreign_key].should == 'qux_id'
82
+ end
83
+
84
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
85
+ reflection, 'foo_bar', 'Bar'
86
+ )
87
+ end
88
+
89
+ it "sets conditions if there are none" do
90
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new) do |macro, name, options, parent|
91
+ options[:conditions].should == "::ts_join_alias::.\"foo_type\" = 'Bar'"
92
+ end
93
+
94
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
95
+ reflection, 'foo_bar', 'Bar'
96
+ )
97
+ end
98
+
99
+ it "appends to the conditions array" do
100
+ options[:conditions] = ['existing']
101
+
102
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new) do |macro, name, options, parent|
103
+ options[:conditions].should == ['existing', "::ts_join_alias::.\"foo_type\" = 'Bar'"]
104
+ end
105
+
106
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
107
+ reflection, 'foo_bar', 'Bar'
108
+ )
109
+ end
110
+
111
+ it "extends the conditions hash" do
112
+ options[:conditions] = {:x => :y}
113
+
114
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new) do |macro, name, options, parent|
115
+ options[:conditions].should == {:x => :y, :foo_type => 'Bar'}
116
+ end
117
+
118
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
119
+ reflection, 'foo_bar', 'Bar'
120
+ )
121
+ end
122
+
123
+ it "appends to the conditions string" do
124
+ options[:conditions] = 'existing'
125
+
126
+ ThinkingSphinx::ActiveRecord::FilteredReflection.should_receive(:new) do |macro, name, options, parent|
127
+ options[:conditions].should == "existing AND ::ts_join_alias::.\"foo_type\" = 'Bar'"
128
+ end
129
+
130
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
131
+ reflection, 'foo_bar', 'Bar'
132
+ )
133
+ end
134
+
135
+ it "returns the new reflection" do
136
+ ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
137
+ reflection, 'foo_bar', 'Bar'
138
+ ).should == filtered_reflection
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::ActiveRecord::Polymorpher do
4
+ let(:polymorpher) { ThinkingSphinx::ActiveRecord::Polymorpher.new source,
5
+ column, class_names }
6
+ let(:source) { double 'Source', :model => outer, :fields => [field],
7
+ :attributes => [attribute] }
8
+ let(:column) { double 'Column', :__name => :foo, :__stack => [:a, :b],
9
+ :__path => [:a, :b, :foo] }
10
+ let(:class_names) { %w( Article Animal ) }
11
+ let(:field) { double :rebase => true }
12
+ let(:attribute) { double :rebase => true }
13
+ let(:outer) { double :reflections => {:a => double(:klass => inner)} }
14
+ let(:inner) { double :reflections => {:b => double(:klass => model)} }
15
+ let(:model) { double 'Model', :reflections => {:foo => reflection} }
16
+ let(:reflection) { double 'Polymorphic Reflection' }
17
+
18
+ describe '#morph!' do
19
+ let(:article_reflection) { double 'Article Reflection' }
20
+ let(:animal_reflection) { double 'Animal Reflection' }
21
+
22
+ before :each do
23
+ ThinkingSphinx::ActiveRecord::FilteredReflection.
24
+ stub(:clone_with_filter).
25
+ and_return(article_reflection, animal_reflection)
26
+ end
27
+
28
+ it "creates a new reflection for each class" do
29
+ ThinkingSphinx::ActiveRecord::FilteredReflection.
30
+ unstub :clone_with_filter
31
+
32
+ ThinkingSphinx::ActiveRecord::FilteredReflection.
33
+ should_receive(:clone_with_filter).
34
+ with(reflection, :foo_article, 'Article').
35
+ and_return(article_reflection)
36
+ ThinkingSphinx::ActiveRecord::FilteredReflection.
37
+ should_receive(:clone_with_filter).
38
+ with(reflection, :foo_animal, 'Animal').
39
+ and_return(animal_reflection)
40
+
41
+ polymorpher.morph!
42
+ end
43
+
44
+ it "adds the new reflections to the end-of-stack model" do
45
+ polymorpher.morph!
46
+
47
+ model.reflections[:foo_article].should == article_reflection
48
+ model.reflections[:foo_animal].should == animal_reflection
49
+ end
50
+
51
+ it "rebases each field" do
52
+ field.should_receive(:rebase).with([:a, :b, :foo],
53
+ :to => [[:a, :b, :foo_article], [:a, :b, :foo_animal]])
54
+
55
+ polymorpher.morph!
56
+ end
57
+
58
+ it "rebases each attribute" do
59
+ attribute.should_receive(:rebase).with([:a, :b, :foo],
60
+ :to => [[:a, :b, :foo_article], [:a, :b, :foo_animal]])
61
+
62
+ polymorpher.morph!
63
+ end
64
+ end
65
+ end
@@ -1,10 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ThinkingSphinx::ActiveRecord::PropertySQLPresenter do
4
- let(:adapter) { double('adapter') }
5
- let(:associations) {
6
- double('associations', :alias_for => 'articles', :aggregate_for? => false)
7
- }
4
+ let(:adapter) { double 'adapter' }
5
+ let(:associations) { double 'associations', :alias_for => 'articles',
6
+ :aggregate_for? => false, :model_for => model }
7
+ let(:model) { double :column_names => ['title', 'created_at'] }
8
8
 
9
9
  before :each do
10
10
  adapter.stub(:quote) { |column| column }
@@ -83,6 +83,18 @@ describe ThinkingSphinx::ActiveRecord::PropertySQLPresenter do
83
83
  presenter.to_select.
84
84
  should == "CONCAT_WS(' ', articles.title, articles.title) AS title"
85
85
  end
86
+
87
+ it "does not include columns that don't exist" do
88
+ adapter.stub :concatenate do |clause, separator|
89
+ "CONCAT_WS('#{separator}', #{clause})"
90
+ end
91
+
92
+ field.stub!(:columns => [column, double('column', :string? => false,
93
+ :__stack => [], :__name => 'body')])
94
+
95
+ presenter.to_select.
96
+ should == "CONCAT_WS(' ', articles.title) AS title"
97
+ end
86
98
  end
87
99
  end
88
100
 
@@ -157,6 +169,18 @@ describe ThinkingSphinx::ActiveRecord::PropertySQLPresenter do
157
169
  presenter.to_select.
158
170
  should == 'UNIX_TIMESTAMP(articles.created_at) AS created_at'
159
171
  end
172
+
173
+ it "does not include columns that don't exist" do
174
+ adapter.stub :concatenate do |clause, separator|
175
+ "CONCAT_WS('#{separator}', #{clause})"
176
+ end
177
+
178
+ attribute.stub!(:columns => [column, double('column',
179
+ :string? => false, :__stack => [], :__name => 'updated_at')])
180
+
181
+ presenter.to_select.
182
+ should == "CONCAT_WS(' ', articles.created_at) AS created_at"
183
+ end
160
184
  end
161
185
  end
162
186
  end
@@ -34,13 +34,23 @@ describe ThinkingSphinx::ActiveRecord::SQLBuilder do
34
34
  end
35
35
 
36
36
  it "adds source associations to the joins of the query" do
37
- source.associations << double('association', :stack => [:user, :posts])
37
+ source.associations << double('association',
38
+ :stack => [:user, :posts], :string? => false)
38
39
 
39
40
  associations.should_receive(:add_join_to).with([:user, :posts])
40
41
 
41
42
  builder.sql_query
42
43
  end
43
44
 
45
+ it "adds string joins directly to the relation" do
46
+ source.associations << double('association',
47
+ :to_s => 'my string', :string? => true)
48
+
49
+ relation.should_receive(:joins).with(['my string']).and_return(relation)
50
+
51
+ builder.sql_query
52
+ end
53
+
44
54
  context 'MySQL adapter' do
45
55
  before :each do
46
56
  source.stub! :type => 'mysql'
@@ -94,6 +94,18 @@ describe ThinkingSphinx::Configuration do
94
94
  config.searchd.query_log.
95
95
  should == File.join(Rails.root, 'log', 'test.searchd.query.log')
96
96
  end
97
+
98
+ it "sets indexer settings if within thinking_sphinx.yml" do
99
+ write_configuration 'mem_limit' => '128M'
100
+
101
+ config.indexer.mem_limit.should == '128M'
102
+ end
103
+
104
+ it "sets searchd settings if within thinking_sphinx.yml" do
105
+ write_configuration 'workers' => 'none'
106
+
107
+ config.searchd.workers.should == 'none'
108
+ end
97
109
  end
98
110
 
99
111
  describe '#next_offset' do
@@ -311,4 +323,16 @@ describe ThinkingSphinx::Configuration do
311
323
  end
312
324
  end
313
325
  end
326
+
327
+ describe '#version' do
328
+ it "defaults to 2.0.6" do
329
+ config.version.should == '2.0.6'
330
+ end
331
+
332
+ it "respects supplied YAML versions" do
333
+ write_configuration 'version' => '2.0.4'
334
+
335
+ config.version.should == '2.0.4'
336
+ end
337
+ end
314
338
  end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::Connection do
4
+ describe '.take' do
5
+ let(:pool) { double }
6
+ let(:connection) { double }
7
+ let(:error) { Mysql2::Error.new '' }
8
+ let(:translated_error) { ThinkingSphinx::SphinxError.new }
9
+
10
+ before :each do
11
+ ThinkingSphinx::Connection.stub :pool => pool
12
+ ThinkingSphinx::SphinxError.stub :new_from_mysql => translated_error
13
+ pool.stub(:take).and_yield(connection)
14
+ end
15
+
16
+ it "yields a connection from the pool" do
17
+ ThinkingSphinx::Connection.take do |c|
18
+ c.should == connection
19
+ end
20
+ end
21
+
22
+ it "retries errors once" do
23
+ tries = 0
24
+
25
+ lambda {
26
+ ThinkingSphinx::Connection.take do |c|
27
+ tries += 1
28
+ raise error if tries < 2
29
+ end
30
+ }.should_not raise_error
31
+ end
32
+
33
+ it "retries errors twice" do
34
+ tries = 0
35
+
36
+ lambda {
37
+ ThinkingSphinx::Connection.take do |c|
38
+ tries += 1
39
+ raise error if tries < 3
40
+ end
41
+ }.should_not raise_error
42
+ end
43
+
44
+ it "raises a translated error if it fails three times" do
45
+ tries = 0
46
+
47
+ lambda {
48
+ ThinkingSphinx::Connection.take do |c|
49
+ tries += 1
50
+ raise error if tries < 4
51
+ end
52
+ }.should raise_error(translated_error)
53
+ end
54
+
55
+ [ThinkingSphinx::SyntaxError, ThinkingSphinx::ParseError].each do |klass|
56
+ context klass.name do
57
+ let(:translated_error) { klass.new }
58
+
59
+ it "raises the error" do
60
+ lambda {
61
+ ThinkingSphinx::Connection.take { |c| raise error }
62
+ }.should raise_error(translated_error)
63
+ end
64
+
65
+ it "does not yield the connection more than once" do
66
+ yields = 0
67
+
68
+ begin
69
+ ThinkingSphinx::Connection.take do |c|
70
+ yields += 1
71
+ raise error
72
+ end
73
+ rescue klass
74
+ #
75
+ end
76
+
77
+ yields.should == 1
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end