thinking-sphinx 4.4.1 → 5.4.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/.circleci/config.yml +218 -0
- data/.travis.yml +12 -21
- data/Appraisals +16 -19
- data/CHANGELOG.markdown +93 -0
- data/README.textile +17 -17
- data/bin/loadsphinx +22 -4
- data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
- data/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb +1 -1
- data/lib/thinking_sphinx/active_record/base.rb +2 -6
- data/lib/thinking_sphinx/active_record/callbacks/association_delta_callbacks.rb +21 -0
- data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +6 -2
- data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +3 -2
- data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
- data/lib/thinking_sphinx/active_record/interpreter.rb +4 -4
- data/lib/thinking_sphinx/active_record/sql_source/template.rb +2 -2
- data/lib/thinking_sphinx/active_record/sql_source.rb +12 -0
- data/lib/thinking_sphinx/active_record.rb +3 -0
- data/lib/thinking_sphinx/callbacks/appender.rb +63 -0
- data/lib/thinking_sphinx/callbacks.rb +9 -0
- data/lib/thinking_sphinx/connection/client.rb +6 -1
- data/lib/thinking_sphinx/connection.rb +4 -0
- data/lib/thinking_sphinx/core/index.rb +1 -2
- data/lib/thinking_sphinx/deletion.rb +18 -17
- data/lib/thinking_sphinx/errors.rb +1 -1
- data/lib/thinking_sphinx/index_set.rb +7 -3
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +1 -1
- data/lib/thinking_sphinx/railtie.rb +20 -7
- data/lib/thinking_sphinx/real_time/index/template.rb +12 -0
- data/lib/thinking_sphinx/real_time/index.rb +5 -3
- data/lib/thinking_sphinx/real_time/interpreter.rb +8 -6
- data/lib/thinking_sphinx/real_time/populator.rb +4 -1
- data/lib/thinking_sphinx/real_time/transcriber.rb +41 -19
- data/lib/thinking_sphinx/real_time/translator.rb +1 -0
- data/lib/thinking_sphinx/search/stale_ids_exception.rb +2 -1
- data/lib/thinking_sphinx/search.rb +1 -1
- data/lib/thinking_sphinx/settings.rb +13 -9
- data/lib/thinking_sphinx/sinatra.rb +1 -1
- data/lib/thinking_sphinx/test.rb +1 -1
- data/lib/thinking_sphinx.rb +0 -3
- data/spec/acceptance/attribute_access_spec.rb +10 -2
- data/spec/acceptance/big_integers_spec.rb +1 -1
- data/spec/acceptance/geosearching_spec.rb +13 -3
- data/spec/acceptance/merging_spec.rb +1 -1
- data/spec/acceptance/paginating_search_results_spec.rb +18 -2
- data/spec/acceptance/real_time_updates_spec.rb +2 -2
- data/spec/acceptance/searching_with_filters_spec.rb +3 -3
- data/spec/acceptance/sql_deltas_spec.rb +16 -4
- data/spec/acceptance/support/sphinx_controller.rb +6 -4
- data/spec/acceptance/support/sphinx_helpers.rb +4 -4
- data/spec/acceptance/suspended_deltas_spec.rb +3 -3
- data/spec/internal/app/indices/article_index.rb +0 -1
- data/spec/internal/app/indices/colour_index.rb +7 -0
- data/spec/internal/app/models/admin/person.rb +3 -1
- data/spec/internal/app/models/album.rb +3 -1
- data/spec/internal/app/models/animal.rb +1 -0
- data/spec/internal/app/models/article.rb +2 -0
- data/spec/internal/app/models/bird.rb +1 -0
- data/spec/internal/app/models/book.rb +2 -0
- data/spec/internal/app/models/car.rb +1 -1
- data/spec/internal/app/models/city.rb +2 -0
- data/spec/internal/app/models/colour.rb +2 -0
- data/spec/internal/app/models/product.rb +1 -1
- data/spec/internal/app/models/tee.rb +5 -0
- data/spec/internal/app/models/user.rb +2 -0
- data/spec/internal/config/database.yml +6 -1
- data/spec/internal/db/schema.rb +1 -0
- data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +2 -1
- data/spec/thinking_sphinx/active_record/index_spec.rb +3 -13
- data/spec/thinking_sphinx/active_record/interpreter_spec.rb +15 -14
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +41 -3
- data/spec/thinking_sphinx/connection/mri_spec.rb +49 -0
- data/spec/thinking_sphinx/deletion_spec.rb +5 -4
- data/spec/thinking_sphinx/index_set_spec.rb +28 -12
- data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +2 -1
- data/spec/thinking_sphinx/real_time/index_spec.rb +51 -13
- data/spec/thinking_sphinx/real_time/interpreter_spec.rb +14 -14
- data/spec/thinking_sphinx/real_time/transcriber_spec.rb +9 -1
- data/thinking-sphinx.gemspec +3 -3
- metadata +17 -12
- data/spec/acceptance/connection_spec.rb +0 -25
@@ -3,7 +3,11 @@
|
|
3
3
|
class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
|
4
4
|
ThinkingSphinx::Callbacks
|
5
5
|
|
6
|
-
callbacks :after_destroy, :after_rollback
|
6
|
+
callbacks :after_commit, :after_destroy, :after_rollback
|
7
|
+
|
8
|
+
def after_commit
|
9
|
+
delete_from_sphinx
|
10
|
+
end
|
7
11
|
|
8
12
|
def after_destroy
|
9
13
|
delete_from_sphinx
|
@@ -27,7 +31,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
|
|
27
31
|
|
28
32
|
def indices
|
29
33
|
ThinkingSphinx::Configuration.instance.index_set_class.new(
|
30
|
-
:classes => [instance.class]
|
34
|
+
:instances => [instance], :classes => [instance.class]
|
31
35
|
).to_a
|
32
36
|
end
|
33
37
|
end
|
@@ -43,8 +43,9 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks <
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def indices
|
46
|
-
@indices ||= config.index_set_class.new(
|
47
|
-
|
46
|
+
@indices ||= config.index_set_class.new(
|
47
|
+
:instances => [instance], :classes => [instance.class]
|
48
|
+
).select { |index| index.type == "plain" }
|
48
49
|
end
|
49
50
|
|
50
51
|
def new_or_changed?
|
@@ -13,15 +13,15 @@ class ThinkingSphinx::ActiveRecord::Interpreter <
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def has(*columns)
|
16
|
-
|
16
|
+
build_properties(
|
17
17
|
::ThinkingSphinx::ActiveRecord::Attribute, columns
|
18
|
-
)
|
18
|
+
).each { |attribute| __source.add_attribute attribute }
|
19
19
|
end
|
20
20
|
|
21
21
|
def indexes(*columns)
|
22
|
-
|
22
|
+
build_properties(
|
23
23
|
::ThinkingSphinx::ActiveRecord::Field, columns
|
24
|
-
)
|
24
|
+
).each { |field| __source.add_field field }
|
25
25
|
end
|
26
26
|
|
27
27
|
def join(*columns)
|
@@ -18,14 +18,14 @@ class ThinkingSphinx::ActiveRecord::SQLSource::Template
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def add_attribute(column, name, type, options = {})
|
21
|
-
source.
|
21
|
+
source.add_attribute ThinkingSphinx::ActiveRecord::Attribute.new(
|
22
22
|
source.model, ThinkingSphinx::ActiveRecord::Column.new(column),
|
23
23
|
options.merge(:as => name, :type => type)
|
24
24
|
)
|
25
25
|
end
|
26
26
|
|
27
27
|
def add_field(column, name, options = {})
|
28
|
-
source.
|
28
|
+
source.add_field ThinkingSphinx::ActiveRecord::Field.new(
|
29
29
|
source.model, ThinkingSphinx::ActiveRecord::Column.new(column),
|
30
30
|
options.merge(:as => name)
|
31
31
|
)
|
@@ -39,6 +39,18 @@ module ThinkingSphinx
|
|
39
39
|
@adapter ||= DatabaseAdapters.adapter_for(@model)
|
40
40
|
end
|
41
41
|
|
42
|
+
def add_attribute(attribute)
|
43
|
+
attributes.delete_if { |existing| existing.name == attribute.name }
|
44
|
+
|
45
|
+
attributes << attribute
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_field(field)
|
49
|
+
fields.delete_if { |existing| existing.name == field.name }
|
50
|
+
|
51
|
+
fields << field
|
52
|
+
end
|
53
|
+
|
42
54
|
def delta_processor
|
43
55
|
options[:delta_processor].try(:new, adapter, @options[:delta_options] || {})
|
44
56
|
end
|
@@ -29,6 +29,7 @@ require 'thinking_sphinx/active_record/source_joins'
|
|
29
29
|
require 'thinking_sphinx/active_record/sql_builder'
|
30
30
|
require 'thinking_sphinx/active_record/sql_source'
|
31
31
|
|
32
|
+
require 'thinking_sphinx/active_record/callbacks/association_delta_callbacks'
|
32
33
|
require 'thinking_sphinx/active_record/callbacks/delete_callbacks'
|
33
34
|
require 'thinking_sphinx/active_record/callbacks/delta_callbacks'
|
34
35
|
require 'thinking_sphinx/active_record/callbacks/update_callbacks'
|
@@ -39,3 +40,5 @@ require 'thinking_sphinx/active_record/depolymorph/conditions_reflection'
|
|
39
40
|
require 'thinking_sphinx/active_record/depolymorph/overridden_reflection'
|
40
41
|
require 'thinking_sphinx/active_record/depolymorph/scoped_reflection'
|
41
42
|
require 'thinking_sphinx/active_record/filter_reflection'
|
43
|
+
|
44
|
+
ActiveRecord::Base.include ThinkingSphinx::ActiveRecord::Base
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ThinkingSphinx::Callbacks::Appender
|
4
|
+
def self.call(model, reference, options, &block)
|
5
|
+
new(model, reference, options, &block).call
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(model, reference, options, &block)
|
9
|
+
@model = model
|
10
|
+
@reference = reference
|
11
|
+
@options = options
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
add_core_callbacks
|
17
|
+
add_delta_callbacks if behaviours.include?(:deltas)
|
18
|
+
add_real_time_callbacks if behaviours.include?(:real_time)
|
19
|
+
add_update_callbacks if behaviours.include?(:updates)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :model, :reference, :options, :block
|
25
|
+
|
26
|
+
def add_core_callbacks
|
27
|
+
model.after_commit(
|
28
|
+
ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks,
|
29
|
+
on: :destroy
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_delta_callbacks
|
34
|
+
if path.empty?
|
35
|
+
model.before_save ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks
|
36
|
+
model.after_commit ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks
|
37
|
+
else
|
38
|
+
model.after_commit(
|
39
|
+
ThinkingSphinx::ActiveRecord::Callbacks::AssociationDeltaCallbacks
|
40
|
+
.new(path)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_real_time_callbacks
|
46
|
+
model.after_commit(
|
47
|
+
ThinkingSphinx::RealTime.callback_for(reference, path, &block),
|
48
|
+
on: [:create, :update]
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_update_callbacks
|
53
|
+
model.after_update ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks
|
54
|
+
end
|
55
|
+
|
56
|
+
def behaviours
|
57
|
+
options[:behaviours] || []
|
58
|
+
end
|
59
|
+
|
60
|
+
def path
|
61
|
+
options[:path] || []
|
62
|
+
end
|
63
|
+
end
|
@@ -3,6 +3,13 @@
|
|
3
3
|
class ThinkingSphinx::Callbacks
|
4
4
|
attr_reader :instance
|
5
5
|
|
6
|
+
def self.append(model, reference = nil, options, &block)
|
7
|
+
reference ||= ThinkingSphinx::Configuration.instance.index_set_class.
|
8
|
+
reference_name(model)
|
9
|
+
|
10
|
+
ThinkingSphinx::Callbacks::Appender.call(model, reference, options, &block)
|
11
|
+
end
|
12
|
+
|
6
13
|
def self.callbacks(*methods)
|
7
14
|
mod = Module.new
|
8
15
|
methods.each do |method|
|
@@ -33,3 +40,5 @@ class ThinkingSphinx::Callbacks
|
|
33
40
|
@instance = instance
|
34
41
|
end
|
35
42
|
end
|
43
|
+
|
44
|
+
require "thinking_sphinx/callbacks/appender"
|
@@ -39,7 +39,7 @@ class ThinkingSphinx::Connection::Client
|
|
39
39
|
private
|
40
40
|
|
41
41
|
def check(statements)
|
42
|
-
if statements.length >
|
42
|
+
if statements.length > maximum_statement_length
|
43
43
|
exception = ThinkingSphinx::QueryLengthError.new
|
44
44
|
exception.statement = statements
|
45
45
|
raise exception
|
@@ -56,6 +56,11 @@ class ThinkingSphinx::Connection::Client
|
|
56
56
|
@client = nil
|
57
57
|
end
|
58
58
|
|
59
|
+
def maximum_statement_length
|
60
|
+
@maximum_statement_length ||= ThinkingSphinx::Configuration.instance.
|
61
|
+
settings['maximum_statement_length']
|
62
|
+
end
|
63
|
+
|
59
64
|
def perform(statements)
|
60
65
|
results_for statements
|
61
66
|
rescue => error
|
@@ -11,7 +11,6 @@ module ThinkingSphinx::Core::Index
|
|
11
11
|
|
12
12
|
def initialize(reference, options = {})
|
13
13
|
@reference = reference.to_sym
|
14
|
-
@docinfo = :extern unless config.settings["skip_docinfo"]
|
15
14
|
@options = options
|
16
15
|
@offset = config.next_offset(options[:offset_as] || reference)
|
17
16
|
@type = 'plain'
|
@@ -40,7 +39,7 @@ module ThinkingSphinx::Core::Index
|
|
40
39
|
def interpret_definition!
|
41
40
|
table_exists = model.table_exists?
|
42
41
|
unless table_exists
|
43
|
-
Rails.logger.info "No table exists for #{model}. Index can not be created"
|
42
|
+
Rails.logger.info "No table exists for #{model}. Index can not be created"
|
44
43
|
return
|
45
44
|
end
|
46
45
|
return if @interpreted_definition
|
@@ -22,10 +22,6 @@ class ThinkingSphinx::Deletion
|
|
22
22
|
|
23
23
|
attr_reader :index, :ids
|
24
24
|
|
25
|
-
def document_ids_for_keys
|
26
|
-
ids.collect { |id| index.document_id_for_key id }
|
27
|
-
end
|
28
|
-
|
29
25
|
def execute(statement)
|
30
26
|
statement = statement.gsub(/\s*\n\s*/, ' ').strip
|
31
27
|
|
@@ -36,11 +32,28 @@ class ThinkingSphinx::Deletion
|
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
35
|
+
class PlainDeletion < ThinkingSphinx::Deletion
|
36
|
+
def perform
|
37
|
+
ids.each_slice(1000) do |some_ids|
|
38
|
+
execute <<-SQL
|
39
|
+
UPDATE #{name}
|
40
|
+
SET sphinx_deleted = 1
|
41
|
+
WHERE sphinx_internal_id IN (#{some_ids.join(', ')})
|
42
|
+
SQL
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
39
47
|
class RealtimeDeletion < ThinkingSphinx::Deletion
|
40
48
|
def perform
|
41
49
|
return unless callbacks_enabled?
|
42
50
|
|
43
|
-
|
51
|
+
ids.each_slice(1000) do |some_ids|
|
52
|
+
execute <<-SQL
|
53
|
+
DELETE FROM #{name}
|
54
|
+
WHERE sphinx_internal_id IN (#{some_ids.join(', ')})
|
55
|
+
SQL
|
56
|
+
end
|
44
57
|
end
|
45
58
|
|
46
59
|
private
|
@@ -54,16 +67,4 @@ class ThinkingSphinx::Deletion
|
|
54
67
|
ThinkingSphinx::Configuration.instance
|
55
68
|
end
|
56
69
|
end
|
57
|
-
|
58
|
-
class PlainDeletion < ThinkingSphinx::Deletion
|
59
|
-
def perform
|
60
|
-
document_ids_for_keys.each_slice(1000) do |document_ids|
|
61
|
-
execute <<-SQL
|
62
|
-
UPDATE #{name}
|
63
|
-
SET sphinx_deleted = 1
|
64
|
-
WHERE id IN (#{document_ids.join(', ')})
|
65
|
-
SQL
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
70
|
end
|
@@ -38,7 +38,7 @@ end
|
|
38
38
|
class ThinkingSphinx::QueryLengthError < ThinkingSphinx::SphinxError
|
39
39
|
def message
|
40
40
|
<<-MESSAGE
|
41
|
-
The supplied SphinxQL statement is #{statement.length} characters long. The maximum allowed length is #{ThinkingSphinx::
|
41
|
+
The supplied SphinxQL statement is #{statement.length} characters long. The maximum allowed length is #{ThinkingSphinx::Configuration.instance.settings['maximum_statement_length']}.
|
42
42
|
|
43
43
|
If this error has been raised during real-time index population, it's probably due to overly large batches of records being processed at once. The default is 1000, but you can lower it on a per-environment basis in config/thinking_sphinx.yml:
|
44
44
|
|
@@ -34,11 +34,11 @@ class ThinkingSphinx::IndexSet
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def classes
|
37
|
-
options[:classes] ||
|
37
|
+
options[:classes] || instances.collect(&:class)
|
38
38
|
end
|
39
39
|
|
40
40
|
def classes_specified?
|
41
|
-
classes.any? || references_specified?
|
41
|
+
instances.any? || classes.any? || references_specified?
|
42
42
|
end
|
43
43
|
|
44
44
|
def classes_and_ancestors
|
@@ -68,6 +68,10 @@ class ThinkingSphinx::IndexSet
|
|
68
68
|
all_indices.select { |index| references.include? index.reference }
|
69
69
|
end
|
70
70
|
|
71
|
+
def instances
|
72
|
+
options[:instances] || []
|
73
|
+
end
|
74
|
+
|
71
75
|
def mti_classes
|
72
76
|
classes.reject { |klass|
|
73
77
|
klass.column_names.include?(klass.inheritance_column)
|
@@ -76,7 +80,7 @@ class ThinkingSphinx::IndexSet
|
|
76
80
|
|
77
81
|
def references
|
78
82
|
options[:references] || classes_and_ancestors.collect { |klass|
|
79
|
-
|
83
|
+
self.class.reference_name(klass)
|
80
84
|
}
|
81
85
|
end
|
82
86
|
|
@@ -133,7 +133,7 @@ class ThinkingSphinx::Middlewares::SphinxQL <
|
|
133
133
|
|
134
134
|
def indices_match_classes?
|
135
135
|
indices.collect(&:reference).uniq.sort == classes.collect { |klass|
|
136
|
-
|
136
|
+
configuration.index_set_class.reference_name(klass)
|
137
137
|
}.sort
|
138
138
|
end
|
139
139
|
|
@@ -5,21 +5,34 @@ class ThinkingSphinx::Railtie < Rails::Railtie
|
|
5
5
|
ThinkingSphinx::Configuration.reset
|
6
6
|
end
|
7
7
|
|
8
|
+
config.after_initialize do
|
9
|
+
require 'thinking_sphinx/active_record'
|
10
|
+
end
|
11
|
+
|
8
12
|
initializer 'thinking_sphinx.initialisation' do
|
9
13
|
ActiveSupport.on_load(:active_record) do
|
10
|
-
|
14
|
+
require 'thinking_sphinx/active_record'
|
11
15
|
end
|
12
16
|
|
13
|
-
if
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
)
|
18
|
-
end
|
17
|
+
if zeitwerk?
|
18
|
+
ActiveSupport::Dependencies.autoload_paths.delete(
|
19
|
+
Rails.root.join("app", "indices").to_s
|
20
|
+
)
|
19
21
|
end
|
22
|
+
|
23
|
+
Rails.application.config.eager_load_paths -=
|
24
|
+
ThinkingSphinx::Configuration.instance.index_paths
|
25
|
+
Rails.application.config.eager_load_paths.freeze
|
20
26
|
end
|
21
27
|
|
22
28
|
rake_tasks do
|
23
29
|
load File.expand_path('../tasks.rb', __FILE__)
|
24
30
|
end
|
31
|
+
|
32
|
+
def zeitwerk?
|
33
|
+
return true if ActiveSupport::VERSION::MAJOR >= 7
|
34
|
+
return false if ActiveSupport::VERSION::MAJOR <= 5
|
35
|
+
|
36
|
+
Rails.application.config.autoloader == :zeitwerk
|
37
|
+
end
|
25
38
|
end
|
@@ -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
|
@@ -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
|
@@ -5,16 +5,18 @@ class ThinkingSphinx::RealTime::Interpreter <
|
|
5
5
|
|
6
6
|
def has(*columns)
|
7
7
|
options = columns.extract_options!
|
8
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
15
|
-
items
|
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
|
@@ -9,6 +9,7 @@ class ThinkingSphinx::Search::StaleIdsException < StandardError
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def message
|
12
|
-
"Record IDs found by Sphinx but not by ActiveRecord : #{ids.join(', ')}"
|
12
|
+
"Record IDs found by Sphinx but not by ActiveRecord : #{ids.join(', ')}\n" \
|
13
|
+
"https://freelancing-gods.com/thinking-sphinx/v5/common_issues.html#record-ids"
|
13
14
|
end
|
14
15
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class ThinkingSphinx::Search < Array
|
4
4
|
CORE_METHODS = %w( == class class_eval extend frozen? id instance_eval
|
5
|
-
instance_of? instance_values instance_variable_defined?
|
5
|
+
instance_exec instance_of? instance_values instance_variable_defined?
|
6
6
|
instance_variable_get instance_variable_set instance_variables is_a?
|
7
7
|
kind_of? member? method methods nil? object_id respond_to?
|
8
8
|
respond_to_missing? send should should_not type )
|