thinking-sphinx 3.1.4 → 3.2.0
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.
- 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
|