thinking-sphinx 3.0.3 → 3.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -1
- data/HISTORY +25 -0
- data/lib/thinking_sphinx.rb +1 -0
- data/lib/thinking_sphinx/active_record/association_proxy.rb +13 -55
- data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +47 -0
- data/lib/thinking_sphinx/active_record/base.rb +16 -15
- data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -12
- data/lib/thinking_sphinx/active_record/database_adapters.rb +41 -42
- data/lib/thinking_sphinx/active_record/polymorpher.rb +7 -2
- data/lib/thinking_sphinx/active_record/property_query.rb +23 -19
- data/lib/thinking_sphinx/active_record/sql_builder.rb +108 -129
- data/lib/thinking_sphinx/active_record/sql_builder/clause_builder.rb +28 -0
- data/lib/thinking_sphinx/active_record/sql_builder/query.rb +43 -0
- data/lib/thinking_sphinx/active_record/sql_builder/statement.rb +110 -0
- data/lib/thinking_sphinx/active_record/sql_source.rb +143 -138
- data/lib/thinking_sphinx/capistrano.rb +11 -8
- data/lib/thinking_sphinx/configuration.rb +57 -35
- data/lib/thinking_sphinx/connection.rb +15 -6
- data/lib/thinking_sphinx/core.rb +1 -0
- data/lib/thinking_sphinx/core/index.rb +18 -10
- data/lib/thinking_sphinx/core/settings.rb +9 -0
- data/lib/thinking_sphinx/deletion.rb +48 -0
- data/lib/thinking_sphinx/errors.rb +7 -0
- data/lib/thinking_sphinx/excerpter.rb +1 -0
- data/lib/thinking_sphinx/facet_search.rb +42 -19
- data/lib/thinking_sphinx/masks/scopes_mask.rb +7 -0
- data/lib/thinking_sphinx/middlewares.rb +27 -33
- data/lib/thinking_sphinx/middlewares/active_record_translator.rb +18 -18
- data/lib/thinking_sphinx/middlewares/geographer.rb +49 -16
- data/lib/thinking_sphinx/middlewares/sphinxql.rb +128 -58
- data/lib/thinking_sphinx/panes/excerpts_pane.rb +7 -3
- data/lib/thinking_sphinx/rake_interface.rb +10 -0
- data/lib/thinking_sphinx/real_time.rb +7 -1
- data/lib/thinking_sphinx/real_time/attribute.rb +4 -0
- data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +14 -10
- data/lib/thinking_sphinx/real_time/index.rb +20 -12
- data/lib/thinking_sphinx/search/glaze.rb +5 -0
- data/lib/thinking_sphinx/search/query.rb +4 -8
- data/lib/thinking_sphinx/tasks.rb +8 -0
- data/spec/acceptance/excerpts_spec.rb +22 -0
- data/spec/acceptance/remove_deleted_records_spec.rb +10 -0
- data/spec/acceptance/searching_across_models_spec.rb +10 -0
- data/spec/acceptance/searching_with_filters_spec.rb +15 -0
- data/spec/acceptance/specifying_sql_spec.rb +3 -3
- data/spec/acceptance/sphinx_scopes_spec.rb +11 -0
- data/spec/internal/app/indices/product_index.rb +2 -0
- data/spec/internal/app/models/categorisation.rb +6 -0
- data/spec/internal/app/models/category.rb +3 -0
- data/spec/internal/app/models/product.rb +4 -1
- data/spec/internal/db/schema.rb +10 -0
- data/spec/thinking_sphinx/active_record/base_spec.rb +33 -0
- data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +4 -35
- data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +5 -10
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +4 -3
- data/spec/thinking_sphinx/configuration_spec.rb +26 -6
- data/spec/thinking_sphinx/connection_spec.rb +4 -1
- data/spec/thinking_sphinx/deletion_spec.rb +76 -0
- data/spec/thinking_sphinx/facet_search_spec.rb +54 -5
- data/spec/thinking_sphinx/panes/excerpts_pane_spec.rb +4 -6
- data/spec/thinking_sphinx/rake_interface_spec.rb +35 -0
- data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +68 -28
- data/spec/thinking_sphinx/search/glaze_spec.rb +19 -0
- data/spec/thinking_sphinx/search/query_spec.rb +39 -2
- data/thinking-sphinx.gemspec +2 -2
- metadata +31 -45
@@ -35,7 +35,7 @@ module ThinkingSphinx::Connection
|
|
35
35
|
pool.take do |connection|
|
36
36
|
begin
|
37
37
|
yield connection
|
38
|
-
rescue Mysql2::Error => error
|
38
|
+
rescue ThinkingSphinx::QueryExecutionError, Mysql2::Error => error
|
39
39
|
original = ThinkingSphinx::SphinxError.new_from_mysql error
|
40
40
|
raise original if original.is_a?(ThinkingSphinx::QueryError)
|
41
41
|
raise Innertube::Pool::BadResource
|
@@ -43,8 +43,12 @@ module ThinkingSphinx::Connection
|
|
43
43
|
end
|
44
44
|
rescue Innertube::Pool::BadResource
|
45
45
|
retries += 1
|
46
|
-
|
47
|
-
|
46
|
+
raise original unless retries < 3
|
47
|
+
|
48
|
+
ActiveSupport::Notifications.instrument(
|
49
|
+
"message.thinking_sphinx", :message => "Retrying query \"#{original.statement}\" after error: #{original.message}"
|
50
|
+
)
|
51
|
+
retry
|
48
52
|
end
|
49
53
|
end
|
50
54
|
|
@@ -65,9 +69,10 @@ module ThinkingSphinx::Connection
|
|
65
69
|
|
66
70
|
def execute(statement)
|
67
71
|
client.query statement
|
68
|
-
rescue
|
69
|
-
|
70
|
-
|
72
|
+
rescue => error
|
73
|
+
wrapper = ThinkingSphinx::QueryExecutionError.new error.message
|
74
|
+
wrapper.statement = statement
|
75
|
+
raise wrapper
|
71
76
|
end
|
72
77
|
|
73
78
|
def query(statement)
|
@@ -78,6 +83,10 @@ module ThinkingSphinx::Connection
|
|
78
83
|
results = [client.query(statements.join('; '))]
|
79
84
|
results << client.store_result while client.next_result
|
80
85
|
results
|
86
|
+
rescue => error
|
87
|
+
wrapper = ThinkingSphinx::QueryExecutionError.new error.message
|
88
|
+
wrapper.statement = statements.join('; ')
|
89
|
+
raise wrapper
|
81
90
|
end
|
82
91
|
end
|
83
92
|
|
data/lib/thinking_sphinx/core.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module ThinkingSphinx::Core::Index
|
2
2
|
extend ActiveSupport::Concern
|
3
|
+
include ThinkingSphinx::Core::Settings
|
3
4
|
|
4
5
|
included do
|
5
6
|
attr_reader :reference, :offset, :options
|
@@ -12,6 +13,7 @@ module ThinkingSphinx::Core::Index
|
|
12
13
|
@charset_type = 'utf-8'
|
13
14
|
@options = options
|
14
15
|
@offset = config.next_offset(reference)
|
16
|
+
@type = 'plain'
|
15
17
|
|
16
18
|
super "#{options[:name] || reference.to_s.gsub('/', '_')}_#{name_suffix}"
|
17
19
|
end
|
@@ -27,10 +29,7 @@ module ThinkingSphinx::Core::Index
|
|
27
29
|
def interpret_definition!
|
28
30
|
return if @interpreted_definition
|
29
31
|
|
30
|
-
|
31
|
-
value = config.settings[setting.to_s]
|
32
|
-
send("#{setting}=", value) unless value.nil?
|
33
|
-
end
|
32
|
+
apply_defaults!
|
34
33
|
|
35
34
|
@interpreted_definition = true
|
36
35
|
interpreter.translate! self, @definition_block if @definition_block
|
@@ -42,19 +41,24 @@ module ThinkingSphinx::Core::Index
|
|
42
41
|
|
43
42
|
def render
|
44
43
|
pre_render
|
44
|
+
set_path
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
if respond_to?(:infix_fields)
|
49
|
-
self.infix_fields = fields.select(&:infixing?).collect(&:name)
|
50
|
-
self.prefix_fields = fields.select(&:prefixing?).collect(&:name)
|
51
|
-
end
|
46
|
+
assign_infix_fields
|
47
|
+
assign_prefix_fields
|
52
48
|
|
53
49
|
super
|
54
50
|
end
|
55
51
|
|
56
52
|
private
|
57
53
|
|
54
|
+
def assign_infix_fields
|
55
|
+
self.infix_fields = fields.select(&:infixing?).collect(&:name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def assign_prefix_fields
|
59
|
+
self.prefix_fields = fields.select(&:prefixing?).collect(&:name)
|
60
|
+
end
|
61
|
+
|
58
62
|
def config
|
59
63
|
ThinkingSphinx::Configuration.instance
|
60
64
|
end
|
@@ -66,4 +70,8 @@ module ThinkingSphinx::Core::Index
|
|
66
70
|
def pre_render
|
67
71
|
interpret_definition!
|
68
72
|
end
|
73
|
+
|
74
|
+
def set_path
|
75
|
+
@path ||= File.join config.indices_location, name
|
76
|
+
end
|
69
77
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class ThinkingSphinx::Deletion
|
2
|
+
delegate :name, :to => :index
|
3
|
+
|
4
|
+
def self.perform(index, instance)
|
5
|
+
{
|
6
|
+
'plain' => PlainDeletion,
|
7
|
+
'rt' => RealtimeDeletion
|
8
|
+
}[index.type].new(index, instance).perform
|
9
|
+
rescue Mysql2::Error => error
|
10
|
+
# This isn't vital, so don't raise the error.
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(index, instance)
|
14
|
+
@index, @instance = index, instance
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :index, :instance
|
20
|
+
|
21
|
+
def connection
|
22
|
+
@connection ||= ThinkingSphinx::Connection.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def document_id_for_key
|
26
|
+
index.document_id_for_key instance.id
|
27
|
+
end
|
28
|
+
|
29
|
+
def execute(statement)
|
30
|
+
ThinkingSphinx::Connection.pool.take do |connection|
|
31
|
+
connection.execute statement
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class RealtimeDeletion < ThinkingSphinx::Deletion
|
36
|
+
def perform
|
37
|
+
execute Riddle::Query::Delete.new(name, document_id_for_key).to_sql
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class PlainDeletion < ThinkingSphinx::Deletion
|
42
|
+
def perform
|
43
|
+
execute Riddle::Query.update(
|
44
|
+
name, document_id_for_key, :sphinx_deleted => true
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
class ThinkingSphinx::SphinxError < StandardError
|
2
|
+
attr_accessor :statement
|
3
|
+
|
2
4
|
def self.new_from_mysql(error)
|
3
5
|
case error.message
|
4
6
|
when /parse error/
|
@@ -12,6 +14,7 @@ class ThinkingSphinx::SphinxError < StandardError
|
|
12
14
|
end
|
13
15
|
|
14
16
|
replacement.set_backtrace error.backtrace
|
17
|
+
replacement.statement = error.statement if error.respond_to?(:statement)
|
15
18
|
replacement
|
16
19
|
end
|
17
20
|
end
|
@@ -25,5 +28,9 @@ end
|
|
25
28
|
class ThinkingSphinx::ParseError < ThinkingSphinx::QueryError
|
26
29
|
end
|
27
30
|
|
31
|
+
class ThinkingSphinx::QueryExecutionError < StandardError
|
32
|
+
attr_accessor :statement
|
33
|
+
end
|
34
|
+
|
28
35
|
class ThinkingSphinx::MixedScopesError < StandardError
|
29
36
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
class ThinkingSphinx::FacetSearch
|
2
2
|
include Enumerable
|
3
3
|
|
4
|
-
attr_reader
|
4
|
+
attr_reader :options
|
5
|
+
attr_accessor :query
|
5
6
|
|
6
7
|
def initialize(query = nil, options = {})
|
7
8
|
query, options = nil, query if query.is_a?(Hash)
|
@@ -36,12 +37,7 @@ class ThinkingSphinx::FacetSearch
|
|
36
37
|
|
37
38
|
batch = ThinkingSphinx::BatchedSearch.new
|
38
39
|
facets.each do |facet|
|
39
|
-
|
40
|
-
:select => '*, @groupby, @count',
|
41
|
-
:group_by => facet.name,
|
42
|
-
:indices => index_names_for(facet)
|
43
|
-
)
|
44
|
-
batch.searches << search
|
40
|
+
batch.searches << ThinkingSphinx::Search.new(query, options_for(facet))
|
45
41
|
end
|
46
42
|
|
47
43
|
batch.populate ThinkingSphinx::Middlewares::RAW_ONLY
|
@@ -64,26 +60,53 @@ class ThinkingSphinx::FacetSearch
|
|
64
60
|
private
|
65
61
|
|
66
62
|
def facets
|
67
|
-
@facets ||=
|
68
|
-
|
69
|
-
|
70
|
-
|
63
|
+
@facets ||= properties.group_by(&:name).collect { |name, matches|
|
64
|
+
ThinkingSphinx::Facet.new name, matches
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def properties
|
69
|
+
properties = indices.collect(&:facets).flatten
|
70
|
+
if options[:facets].present?
|
71
|
+
properties = properties.select { |property|
|
72
|
+
options[:facets].include? property.name.to_sym
|
71
73
|
}
|
72
74
|
end
|
75
|
+
properties
|
73
76
|
end
|
74
77
|
|
75
78
|
def index_names_for(*facets)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
facet_names(
|
80
|
+
indices.select do |index|
|
81
|
+
facets.all? { |facet| facet_names(index.facets).include?(facet.name) }
|
82
|
+
end
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def facet_names(facets)
|
87
|
+
facets.collect(&:name)
|
82
88
|
end
|
83
89
|
|
84
90
|
def indices
|
85
|
-
@indices ||= ThinkingSphinx::IndexSet.new options[:classes],
|
86
|
-
|
91
|
+
@indices ||= ThinkingSphinx::IndexSet.new options[:classes], options[:indices]
|
92
|
+
end
|
93
|
+
|
94
|
+
def max_matches
|
95
|
+
ThinkingSphinx::Configuration.instance.settings['max_matches'] || 1000
|
96
|
+
end
|
97
|
+
|
98
|
+
def limit
|
99
|
+
limit = options[:limit] || options[:per_page] || max_matches
|
100
|
+
end
|
101
|
+
|
102
|
+
def options_for(facet)
|
103
|
+
options.merge(
|
104
|
+
:select => '*, @groupby, @count',
|
105
|
+
:group_by => facet.name,
|
106
|
+
:indices => index_names_for(facet),
|
107
|
+
:max_matches => limit,
|
108
|
+
:limit => limit
|
109
|
+
)
|
87
110
|
end
|
88
111
|
|
89
112
|
class Filter
|
@@ -7,6 +7,13 @@ class ThinkingSphinx::Masks::ScopesMask
|
|
7
7
|
public_methods(false).include?(method) || can_apply_scope?(method)
|
8
8
|
end
|
9
9
|
|
10
|
+
def facets(query = nil, options = {})
|
11
|
+
search = ThinkingSphinx.facets query, options
|
12
|
+
ThinkingSphinx::Search::Merger.new(search).merge!(
|
13
|
+
@search.query, @search.options
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
10
17
|
def search(query = nil, options = {})
|
11
18
|
query, options = nil, query if query.is_a?(Hash)
|
12
19
|
ThinkingSphinx::Search::Merger.new(@search).merge! query, options
|
@@ -1,39 +1,33 @@
|
|
1
|
-
module ThinkingSphinx::Middlewares
|
2
|
-
|
1
|
+
module ThinkingSphinx::Middlewares; end
|
2
|
+
|
3
|
+
%w[middleware active_record_translator geographer glazier ids_only inquirer
|
4
|
+
sphinxql stale_id_checker stale_id_filter utf8].each do |middleware|
|
5
|
+
require "thinking_sphinx/middlewares/#{middleware}"
|
3
6
|
end
|
4
7
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
require 'thinking_sphinx/middlewares/sphinxql'
|
12
|
-
require 'thinking_sphinx/middlewares/stale_id_checker'
|
13
|
-
require 'thinking_sphinx/middlewares/stale_id_filter'
|
14
|
-
require 'thinking_sphinx/middlewares/utf8'
|
8
|
+
module ThinkingSphinx::Middlewares
|
9
|
+
def self.use(builder, middlewares)
|
10
|
+
middlewares.each { |m| builder.use m }
|
11
|
+
end
|
12
|
+
|
13
|
+
BASE_MIDDLEWARES = [SphinxQL, Geographer, Inquirer]
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
use ThinkingSphinx::Middlewares::Glazier
|
25
|
-
end
|
15
|
+
DEFAULT = ::Middleware::Builder.new do
|
16
|
+
use StaleIdFilter
|
17
|
+
ThinkingSphinx::Middlewares.use self, BASE_MIDDLEWARES
|
18
|
+
use UTF8
|
19
|
+
use ActiveRecordTranslator
|
20
|
+
use StaleIdChecker
|
21
|
+
use Glazier
|
22
|
+
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
use ThinkingSphinx::Middlewares::UTF8
|
32
|
-
end
|
24
|
+
RAW_ONLY = ::Middleware::Builder.new do
|
25
|
+
ThinkingSphinx::Middlewares.use self, BASE_MIDDLEWARES
|
26
|
+
use UTF8
|
27
|
+
end
|
33
28
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
use ThinkingSphinx::Middlewares::IdsOnly
|
29
|
+
IDS_ONLY = ::Middleware::Builder.new do
|
30
|
+
ThinkingSphinx::Middlewares.use self, BASE_MIDDLEWARES
|
31
|
+
use IdsOnly
|
32
|
+
end
|
39
33
|
end
|
@@ -18,7 +18,7 @@ class ThinkingSphinx::Middlewares::ActiveRecordTranslator <
|
|
18
18
|
|
19
19
|
def call
|
20
20
|
results_for_models # load now to avoid segfaults
|
21
|
-
context[:results] = context[:results].collect { |row| result_for
|
21
|
+
context[:results] = context[:results].collect { |row| result_for(row) }
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
@@ -26,11 +26,9 @@ class ThinkingSphinx::Middlewares::ActiveRecordTranslator <
|
|
26
26
|
attr_reader :context
|
27
27
|
|
28
28
|
def ids_for_model(model_name)
|
29
|
-
context[:results].
|
30
|
-
row['sphinx_internal_class'] == model_name
|
31
|
-
}.
|
32
|
-
row['sphinx_internal_id']
|
33
|
-
}
|
29
|
+
context[:results].collect { |row|
|
30
|
+
row['sphinx_internal_id'] if row['sphinx_internal_class'] == model_name
|
31
|
+
}.compact
|
34
32
|
end
|
35
33
|
|
36
34
|
def model_names
|
@@ -51,20 +49,22 @@ class ThinkingSphinx::Middlewares::ActiveRecordTranslator <
|
|
51
49
|
end
|
52
50
|
|
53
51
|
def results_for_models
|
54
|
-
@results_for_models ||= model_names.inject({})
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
relation = relation.includes sql_options[:include] if sql_options[:include]
|
60
|
-
relation = relation.joins sql_options[:joins] if sql_options[:joins]
|
61
|
-
relation = relation.order sql_options[:order] if sql_options[:order]
|
62
|
-
relation = relation.select sql_options[:select] if sql_options[:select]
|
63
|
-
|
64
|
-
hash[name] = relation.where(model.primary_key => ids)
|
52
|
+
@results_for_models ||= model_names.inject({}) do |hash, name|
|
53
|
+
model = name.constantize
|
54
|
+
hash[name] = model_relation_with_sql_options(model.unscoped).where(
|
55
|
+
model.primary_key => ids_for_model(name)
|
56
|
+
)
|
65
57
|
|
66
58
|
hash
|
67
|
-
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def model_relation_with_sql_options(relation)
|
63
|
+
relation = relation.includes sql_options[:include] if sql_options[:include]
|
64
|
+
relation = relation.joins sql_options[:joins] if sql_options[:joins]
|
65
|
+
relation = relation.order sql_options[:order] if sql_options[:order]
|
66
|
+
relation = relation.select sql_options[:select] if sql_options[:select]
|
67
|
+
relation
|
68
68
|
end
|
69
69
|
|
70
70
|
def sql_options
|