thinking-sphinx 3.1.4 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +7 -2
- data/Appraisals +4 -4
- data/Gemfile +2 -0
- data/HISTORY +24 -0
- data/README.textile +5 -4
- data/gemfiles/rails_3_2.gemfile +2 -1
- data/gemfiles/rails_4_0.gemfile +2 -1
- data/gemfiles/rails_4_1.gemfile +2 -1
- data/gemfiles/rails_4_2.gemfile +2 -1
- data/lib/thinking_sphinx.rb +4 -1
- data/lib/thinking_sphinx/active_record.rb +1 -0
- data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
- data/lib/thinking_sphinx/active_record/attribute/type.rb +1 -1
- data/lib/thinking_sphinx/active_record/base.rb +1 -1
- data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -1
- data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +7 -3
- data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +2 -2
- data/lib/thinking_sphinx/active_record/column_sql_presenter.rb +5 -1
- data/lib/thinking_sphinx/active_record/index.rb +4 -4
- data/lib/thinking_sphinx/active_record/source_joins.rb +55 -0
- data/lib/thinking_sphinx/active_record/sql_builder.rb +3 -18
- data/lib/thinking_sphinx/active_record/sql_builder/query.rb +7 -0
- data/lib/thinking_sphinx/active_record/sql_source.rb +7 -5
- data/lib/thinking_sphinx/active_record/sql_source/template.rb +1 -1
- data/lib/thinking_sphinx/callbacks.rb +18 -0
- data/lib/thinking_sphinx/configuration.rb +50 -33
- data/lib/thinking_sphinx/configuration/duplicate_names.rb +34 -0
- data/lib/thinking_sphinx/connection.rb +4 -4
- data/lib/thinking_sphinx/controller.rb +6 -4
- data/lib/thinking_sphinx/deletion.rb +13 -0
- data/lib/thinking_sphinx/errors.rb +8 -0
- data/lib/thinking_sphinx/index_set.rb +6 -1
- data/lib/thinking_sphinx/indexing_strategies/all_at_once.rb +7 -0
- data/lib/thinking_sphinx/indexing_strategies/one_at_a_time.rb +14 -0
- data/lib/thinking_sphinx/middlewares/active_record_translator.rb +1 -1
- data/lib/thinking_sphinx/middlewares/inquirer.rb +1 -1
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +2 -2
- data/lib/thinking_sphinx/middlewares/stale_id_checker.rb +1 -1
- data/lib/thinking_sphinx/middlewares/stale_id_filter.rb +2 -2
- data/lib/thinking_sphinx/rake_interface.rb +14 -5
- data/lib/thinking_sphinx/real_time.rb +1 -0
- data/lib/thinking_sphinx/real_time/attribute.rb +7 -1
- data/lib/thinking_sphinx/real_time/index.rb +2 -0
- data/lib/thinking_sphinx/real_time/populator.rb +3 -3
- data/lib/thinking_sphinx/real_time/property.rb +1 -5
- data/lib/thinking_sphinx/real_time/transcriber.rb +44 -9
- data/lib/thinking_sphinx/real_time/translator.rb +36 -0
- data/lib/thinking_sphinx/search.rb +10 -0
- data/lib/thinking_sphinx/search/context.rb +8 -0
- data/lib/thinking_sphinx/search/stale_ids_exception.rb +3 -2
- data/lib/thinking_sphinx/subscribers/populator_subscriber.rb +1 -1
- data/lib/thinking_sphinx/tasks.rb +3 -1
- data/spec/acceptance/remove_deleted_records_spec.rb +18 -0
- data/spec/acceptance/searching_with_filters_spec.rb +13 -0
- data/spec/internal/app/indices/product_index.rb +1 -0
- data/spec/internal/db/schema.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/json_column.rb +29 -0
- data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +10 -0
- data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +10 -0
- data/spec/thinking_sphinx/active_record/column_sql_presenter_spec.rb +37 -0
- data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +7 -17
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +43 -15
- data/spec/thinking_sphinx/errors_spec.rb +7 -0
- data/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb +15 -1
- data/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb +1 -0
- data/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb +26 -6
- data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +4 -4
- data/spec/thinking_sphinx_spec.rb +2 -1
- data/thinking-sphinx.gemspec +1 -1
- metadata +12 -3
@@ -15,5 +15,6 @@ require 'thinking_sphinx/real_time/index'
|
|
15
15
|
require 'thinking_sphinx/real_time/interpreter'
|
16
16
|
require 'thinking_sphinx/real_time/populator'
|
17
17
|
require 'thinking_sphinx/real_time/transcriber'
|
18
|
+
require 'thinking_sphinx/real_time/translator'
|
18
19
|
|
19
20
|
require 'thinking_sphinx/real_time/callbacks/real_time_callbacks'
|
@@ -8,7 +8,9 @@ class ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def translate(object)
|
11
|
-
super || default_value
|
11
|
+
output = super || default_value
|
12
|
+
|
13
|
+
json? ? output.to_json : output
|
12
14
|
end
|
13
15
|
|
14
16
|
private
|
@@ -16,4 +18,8 @@ class ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property
|
|
16
18
|
def default_value
|
17
19
|
type == :string ? '' : 0
|
18
20
|
end
|
21
|
+
|
22
|
+
def json?
|
23
|
+
type == :json
|
24
|
+
end
|
19
25
|
end
|
@@ -69,6 +69,8 @@ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
|
|
69
69
|
@rt_attr_float
|
70
70
|
when :bigint
|
71
71
|
attribute.multi? ? @rt_attr_multi_64 : @rt_attr_bigint
|
72
|
+
when :json
|
73
|
+
@rt_attr_json
|
72
74
|
else
|
73
75
|
raise "Unknown attribute type '#{attribute.type}'"
|
74
76
|
end
|
@@ -12,9 +12,9 @@ class ThinkingSphinx::RealTime::Populator
|
|
12
12
|
|
13
13
|
remove_files
|
14
14
|
|
15
|
-
scope.
|
16
|
-
transcriber.copy
|
17
|
-
instrument 'populated', :
|
15
|
+
scope.find_in_batches do |instances|
|
16
|
+
transcriber.copy *instances
|
17
|
+
instrument 'populated', :instances => instances
|
18
18
|
end
|
19
19
|
|
20
20
|
controller.rotate
|
@@ -14,10 +14,6 @@ class ThinkingSphinx::RealTime::Property
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def translate(object)
|
17
|
-
|
18
|
-
|
19
|
-
base = @column.__stack.inject(object) { |base, node| base.try(node) }
|
20
|
-
base = base.try(@column.__name)
|
21
|
-
base.is_a?(String) ? base.gsub("\u0000", '') : base
|
17
|
+
ThinkingSphinx::RealTime::Translator.call(object, @column)
|
22
18
|
end
|
23
19
|
end
|
@@ -3,22 +3,47 @@ class ThinkingSphinx::RealTime::Transcriber
|
|
3
3
|
@index = index
|
4
4
|
end
|
5
5
|
|
6
|
-
def copy(
|
7
|
-
|
6
|
+
def copy(*instances)
|
7
|
+
items = instances.select { |instance|
|
8
|
+
instance.persisted? && copy?(instance)
|
9
|
+
}
|
10
|
+
return unless items.present?
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
values << property.translate(instance)
|
13
|
-
end
|
12
|
+
values = items.collect { |instance|
|
13
|
+
TranscribeInstance.call(instance, index, properties)
|
14
|
+
}
|
14
15
|
|
15
16
|
insert = Riddle::Query::Insert.new index.name, columns, values
|
16
17
|
sphinxql = insert.replace!.to_sql
|
17
|
-
|
18
|
+
|
18
19
|
ThinkingSphinx::Logger.log :query, sphinxql do
|
19
20
|
ThinkingSphinx::Connection.take do |connection|
|
20
21
|
connection.execute sphinxql
|
21
|
-
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class TranscribeInstance
|
27
|
+
def self.call(instance, index, properties)
|
28
|
+
new(instance, index, properties).call
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(instance, index, properties)
|
32
|
+
@instance, @index, @properties = instance, index, properties
|
33
|
+
end
|
34
|
+
|
35
|
+
def call
|
36
|
+
properties.each_with_object([document_id]) do |property, instance_values|
|
37
|
+
instance_values << property.translate(instance)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :instance, :index, :properties
|
44
|
+
|
45
|
+
def document_id
|
46
|
+
index.document_id_for_key instance.id
|
22
47
|
end
|
23
48
|
end
|
24
49
|
|
@@ -26,6 +51,12 @@ class ThinkingSphinx::RealTime::Transcriber
|
|
26
51
|
|
27
52
|
attr_reader :index
|
28
53
|
|
54
|
+
def columns
|
55
|
+
@columns ||= properties.each_with_object(['id']) do |property, columns|
|
56
|
+
columns << property.name
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
29
60
|
def copy?(instance)
|
30
61
|
index.conditions.empty? || index.conditions.all? { |condition|
|
31
62
|
case condition
|
@@ -38,4 +69,8 @@ class ThinkingSphinx::RealTime::Transcriber
|
|
38
69
|
end
|
39
70
|
}
|
40
71
|
end
|
72
|
+
|
73
|
+
def properties
|
74
|
+
@properties ||= index.fields + index.attributes
|
75
|
+
end
|
41
76
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class ThinkingSphinx::RealTime::Translator
|
2
|
+
def self.call(object, column)
|
3
|
+
new(object, column).call
|
4
|
+
end
|
5
|
+
|
6
|
+
def initialize(object, column)
|
7
|
+
@object, @column = object, column
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
return name unless name.is_a?(Symbol)
|
12
|
+
return result unless result.is_a?(String)
|
13
|
+
|
14
|
+
result.gsub "\u0000", ''
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :object, :column
|
20
|
+
|
21
|
+
def name
|
22
|
+
@column.__name
|
23
|
+
end
|
24
|
+
|
25
|
+
def owner
|
26
|
+
stack.inject(object) { |previous, node| previous.try node }
|
27
|
+
end
|
28
|
+
|
29
|
+
def result
|
30
|
+
@result ||= owner.try name
|
31
|
+
end
|
32
|
+
|
33
|
+
def stack
|
34
|
+
@column.__stack
|
35
|
+
end
|
36
|
+
end
|
@@ -38,6 +38,16 @@ class ThinkingSphinx::Search < Array
|
|
38
38
|
options[:page].to_i
|
39
39
|
end
|
40
40
|
|
41
|
+
def marshal_dump
|
42
|
+
populate
|
43
|
+
|
44
|
+
[@populated, @query, @options, @context]
|
45
|
+
end
|
46
|
+
|
47
|
+
def marshal_load(array)
|
48
|
+
@populated, @query, @options, @context = array
|
49
|
+
end
|
50
|
+
|
41
51
|
def masks
|
42
52
|
@masks ||= @options[:masks] || DEFAULT_MASKS.clone
|
43
53
|
end
|
@@ -33,6 +33,24 @@ describe 'Hiding deleted records from search results', :live => true do
|
|
33
33
|
should be_empty
|
34
34
|
end
|
35
35
|
|
36
|
+
it "does not remove real-time results when callbacks are disabled" do
|
37
|
+
original = ThinkingSphinx::Configuration.instance.
|
38
|
+
settings['real_time_callbacks']
|
39
|
+
product = Product.create! :name => 'Shiny'
|
40
|
+
Product.search('Shiny', :indices => ['product_core']).to_a.
|
41
|
+
should == [product]
|
42
|
+
|
43
|
+
ThinkingSphinx::Configuration.instance.
|
44
|
+
settings['real_time_callbacks'] = false
|
45
|
+
|
46
|
+
product.destroy
|
47
|
+
Product.search_for_ids('Shiny', :indices => ['product_core']).
|
48
|
+
should_not be_empty
|
49
|
+
|
50
|
+
ThinkingSphinx::Configuration.instance.
|
51
|
+
settings['real_time_callbacks'] = original
|
52
|
+
end
|
53
|
+
|
36
54
|
it "deletes STI child classes from parent indices" do
|
37
55
|
duck = Bird.create :name => 'Duck'
|
38
56
|
index
|
@@ -141,4 +141,17 @@ describe 'Searching with filters', :live => true do
|
|
141
141
|
products = Product.search :with => {:category_ids => [flat.id]}
|
142
142
|
products.to_a.should == [pancakes]
|
143
143
|
end
|
144
|
+
|
145
|
+
it 'searches with real-time JSON attributes' do
|
146
|
+
pancakes = Product.create :name => 'Pancakes',
|
147
|
+
:options => {'lemon' => 1, 'sugar' => 1, :number => 3}
|
148
|
+
waffles = Product.create :name => 'Waffles',
|
149
|
+
:options => {'chocolate' => 1, 'sugar' => 1, :number => 1}
|
150
|
+
|
151
|
+
products = Product.search :with => {"options.lemon" => 1}
|
152
|
+
products.to_a.should == [pancakes]
|
153
|
+
|
154
|
+
products = Product.search :with => {"options.sugar" => 1}
|
155
|
+
products.to_a.should == [pancakes, waffles]
|
156
|
+
end if JSONColumn.call
|
144
157
|
end
|
data/spec/internal/db/schema.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
class JSONColumn
|
2
|
+
include ActiveRecord::ConnectionAdapters
|
3
|
+
|
4
|
+
def self.call
|
5
|
+
new.call
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
postgresql? && column?
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def column?
|
15
|
+
(
|
16
|
+
ActiveRecord::ConnectionAdapters.constants.include?(:PostgreSQLAdapter) &&
|
17
|
+
PostgreSQLAdapter.constants.include?(:TableDefinition) &&
|
18
|
+
PostgreSQLAdapter::TableDefinition.instance_methods.include?(:json)
|
19
|
+
) || (
|
20
|
+
ActiveRecord::ConnectionAdapters.constants.include?(:PostgreSQL) &&
|
21
|
+
PostgreSQL.constants.include?(:ColumnMethods) &&
|
22
|
+
PostgreSQL::ColumnMethods.instance_methods.include?(:json)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def postgresql?
|
27
|
+
ENV['DATABASE'] == 'postgresql'
|
28
|
+
end
|
29
|
+
end
|
@@ -53,5 +53,15 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks do
|
|
53
53
|
|
54
54
|
callbacks.after_destroy
|
55
55
|
end
|
56
|
+
|
57
|
+
it 'does nothing if callbacks are suspended' do
|
58
|
+
ThinkingSphinx::Callbacks.suspend!
|
59
|
+
|
60
|
+
ThinkingSphinx::Deletion.should_not_receive(:perform)
|
61
|
+
|
62
|
+
callbacks.after_destroy
|
63
|
+
|
64
|
+
ThinkingSphinx::Callbacks.resume!
|
65
|
+
end
|
56
66
|
end
|
57
67
|
end
|
@@ -70,5 +70,15 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
|
|
70
70
|
|
71
71
|
lambda { callbacks.after_update }.should_not raise_error
|
72
72
|
end
|
73
|
+
|
74
|
+
it 'does nothing if callbacks are suspended' do
|
75
|
+
ThinkingSphinx::Callbacks.suspend!
|
76
|
+
|
77
|
+
connection.should_not_receive(:execute)
|
78
|
+
|
79
|
+
callbacks.after_update
|
80
|
+
|
81
|
+
ThinkingSphinx::Callbacks.resume!
|
82
|
+
end
|
73
83
|
end
|
74
84
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ThinkingSphinx::ActiveRecord::ColumnSQLPresenter do
|
4
|
+
describe '#with_table' do
|
5
|
+
let(:model) { double 'Model' }
|
6
|
+
let(:column) { double 'Column', :__name => 'column_name',
|
7
|
+
:__stack => [], :string? => false }
|
8
|
+
let(:adapter) { double 'Adapter' }
|
9
|
+
let(:associations) { double 'Associations' }
|
10
|
+
let(:path) { double 'Path',
|
11
|
+
:model => double(:column_names => ['column_name']) }
|
12
|
+
let(:presenter) { ThinkingSphinx::ActiveRecord::ColumnSQLPresenter.new(
|
13
|
+
model, column, adapter, associations
|
14
|
+
) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
stub_const 'Joiner::Path', double(:new => path)
|
18
|
+
adapter.stub(:quote) { |arg| "`#{arg}`" }
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when there's no explicit db name" do
|
22
|
+
before { associations.stub(:alias_for => 'table_name') }
|
23
|
+
|
24
|
+
it 'returns quoted table and column names' do
|
25
|
+
presenter.with_table.should == '`table_name`.`column_name`'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when an eplicit db name is provided' do
|
30
|
+
before { associations.stub(:alias_for => 'db_name.table_name') }
|
31
|
+
|
32
|
+
it 'returns properly quoted table name with column name' do
|
33
|
+
presenter.with_table.should == '`db_name`.`table_name`.`column_name`'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -5,7 +5,7 @@ describe ThinkingSphinx::ActiveRecord::SQLBuilder do
|
|
5
5
|
:fields => [], :attributes => [], :disable_range? => false,
|
6
6
|
:delta_processor => nil, :conditions => [], :groupings => [],
|
7
7
|
:adapter => adapter, :associations => [], :primary_key => :id,
|
8
|
-
:options => {}) }
|
8
|
+
:options => {}, :properties => []) }
|
9
9
|
let(:model) { double('model', :connection => connection,
|
10
10
|
:descends_from_active_record? => true, :column_names => [],
|
11
11
|
:inheritance_column => 'type', :unscoped => relation,
|
@@ -518,22 +518,6 @@ describe ThinkingSphinx::ActiveRecord::SQLBuilder do
|
|
518
518
|
end
|
519
519
|
end
|
520
520
|
|
521
|
-
describe 'sql_query_post_index' do
|
522
|
-
let(:processor) { double('processor', :reset_query => 'RESET DELTAS') }
|
523
|
-
|
524
|
-
it "adds a reset delta query if there is a delta processor and this is the core source" do
|
525
|
-
source.stub :delta_processor => processor, :delta? => false
|
526
|
-
|
527
|
-
builder.sql_query_post_index.should include('RESET DELTAS')
|
528
|
-
end
|
529
|
-
|
530
|
-
it "adds no reset delta query if there is a delta processor and this is the delta source" do
|
531
|
-
source.stub :delta_processor => processor, :delta? => true
|
532
|
-
|
533
|
-
builder.sql_query_post_index.should_not include('RESET DELTAS')
|
534
|
-
end
|
535
|
-
end
|
536
|
-
|
537
521
|
describe 'sql_query_pre' do
|
538
522
|
let(:processor) { double('processor', :reset_query => 'RESET DELTAS') }
|
539
523
|
|
@@ -542,6 +526,12 @@ describe ThinkingSphinx::ActiveRecord::SQLBuilder do
|
|
542
526
|
adapter.stub :utf8_query_pre => ['SET UTF8']
|
543
527
|
end
|
544
528
|
|
529
|
+
it "adds a reset delta query if there is a delta processor and this is the core source" do
|
530
|
+
source.stub :delta_processor => processor
|
531
|
+
|
532
|
+
builder.sql_query_pre.should include('RESET DELTAS')
|
533
|
+
end
|
534
|
+
|
545
535
|
it "does not add a reset query if there is no delta processor" do
|
546
536
|
builder.sql_query_pre.should_not include('RESET DELTAS')
|
547
537
|
end
|