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
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
1
3
|
class ThinkingSphinx::Middlewares::Geographer <
|
2
4
|
ThinkingSphinx::Middlewares::Middleware
|
3
5
|
|
@@ -9,6 +11,8 @@ class ThinkingSphinx::Middlewares::Geographer <
|
|
9
11
|
app.call contexts
|
10
12
|
end
|
11
13
|
|
14
|
+
private
|
15
|
+
|
12
16
|
class Inner
|
13
17
|
def initialize(context)
|
14
18
|
@context = context
|
@@ -25,29 +29,58 @@ class ThinkingSphinx::Middlewares::Geographer <
|
|
25
29
|
|
26
30
|
attr_reader :context
|
27
31
|
|
28
|
-
|
29
|
-
@attribute_names ||= context[:indices].collect(&:unique_attribute_names).
|
30
|
-
flatten.uniq
|
31
|
-
end
|
32
|
+
delegate :geo, :latitude, :longitude, :to => :geolocation_attributes
|
32
33
|
|
33
|
-
def
|
34
|
-
context
|
34
|
+
def geolocation_attributes
|
35
|
+
@geolocation_attributes ||= GeolocationAttributes.new(context)
|
35
36
|
end
|
36
37
|
|
37
38
|
def geodist_clause
|
38
|
-
"GEODIST(#{geo.first}, #{geo.last}, #{
|
39
|
+
"GEODIST(#{geo.first}, #{geo.last}, #{latitude}, #{longitude}) AS geodist"
|
39
40
|
end
|
40
41
|
|
41
|
-
|
42
|
-
context
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
class GeolocationAttributes
|
43
|
+
def initialize(context)
|
44
|
+
self.context = context
|
45
|
+
self.latitude = latitude_attr if latitude_attr
|
46
|
+
self.longitude = longitude_attr if longitude_attr
|
47
|
+
end
|
48
|
+
|
49
|
+
def geo
|
50
|
+
search_context_options[:geo]
|
51
|
+
end
|
52
|
+
attr_accessor :latitude, :longitude
|
53
|
+
|
54
|
+
def latitude
|
55
|
+
@latitude ||= names.detect { |name| %w[lat latitude].include?(name) } || 'lat'
|
56
|
+
end
|
57
|
+
|
58
|
+
def longitude
|
59
|
+
@longitude ||= names.detect { |name| %w[lng longitude].include?(name) } || 'lng'
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
attr_accessor :context
|
64
|
+
|
65
|
+
def latitude_attr
|
66
|
+
@latitude_attr ||= search_context_options[:latitude_attr]
|
67
|
+
end
|
68
|
+
|
69
|
+
def longitude_attr
|
70
|
+
@longitude_attr ||= search_context_options[:longitude_attr]
|
71
|
+
end
|
72
|
+
|
73
|
+
def indices
|
74
|
+
context[:indices]
|
75
|
+
end
|
76
|
+
|
77
|
+
def names
|
78
|
+
@names ||= indices.collect(&:unique_attribute_names).flatten.uniq
|
79
|
+
end
|
46
80
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
attribute_names.detect { |attribute| attribute == 'longitude' } || 'lng'
|
81
|
+
def search_context_options
|
82
|
+
@search_context_options ||= context.search.options
|
83
|
+
end
|
51
84
|
end
|
52
85
|
end
|
53
86
|
end
|
@@ -37,11 +37,26 @@ class ThinkingSphinx::Middlewares::SphinxQL <
|
|
37
37
|
classes + descendants
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
41
|
-
|
42
|
-
name
|
40
|
+
def classes_and_descendants_names
|
41
|
+
classes_and_descendants.collect do |klass|
|
42
|
+
name = klass.name
|
43
|
+
name = %Q{"#{name}"} if name[/:/]
|
44
|
+
name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def classes_with_inheritance_column
|
49
|
+
classes.select { |klass|
|
50
|
+
klass.column_names.include?(klass.inheritance_column)
|
43
51
|
}
|
44
|
-
|
52
|
+
end
|
53
|
+
|
54
|
+
def class_condition
|
55
|
+
"(#{classes_and_descendants_names.join('|')})"
|
56
|
+
end
|
57
|
+
|
58
|
+
def constantize_inheritance_column(klass)
|
59
|
+
klass.connection.select_values(inheritance_column_select(klass)).compact.each(&:constantize)
|
45
60
|
end
|
46
61
|
|
47
62
|
def descendants
|
@@ -49,51 +64,45 @@ class ThinkingSphinx::Middlewares::SphinxQL <
|
|
49
64
|
end
|
50
65
|
|
51
66
|
def descendants_from_tables
|
52
|
-
|
53
|
-
|
54
|
-
}.collect { |klass|
|
55
|
-
klass.connection.select_values(<<-SQL).compact.each(&:constantize)
|
56
|
-
SELECT DISTINCT #{klass.inheritance_column}
|
57
|
-
FROM #{klass.table_name}
|
58
|
-
SQL
|
67
|
+
classes_with_inheritance_column.collect do |klass|
|
68
|
+
constantize_inheritance_column(klass)
|
59
69
|
klass.descendants
|
60
|
-
|
70
|
+
end.flatten
|
71
|
+
end
|
72
|
+
|
73
|
+
def inheritance_column_select(klass)
|
74
|
+
<<-SQL
|
75
|
+
SELECT DISTINCT #{klass.inheritance_column}
|
76
|
+
FROM #{klass.table_name}
|
77
|
+
SQL
|
61
78
|
end
|
62
79
|
|
63
80
|
def exclusive_filters
|
64
81
|
@exclusive_filters ||= (options[:without] || {}).tap do |without|
|
65
|
-
|
66
|
-
without[:sphinx_internal_id] = options[:without_ids]
|
67
|
-
end
|
82
|
+
without[:sphinx_internal_id] = options[:without_ids] if options[:without_ids].present?
|
68
83
|
end
|
69
84
|
end
|
70
85
|
|
71
86
|
def extended_query
|
72
87
|
conditions = options[:conditions] || {}
|
73
88
|
conditions[:sphinx_internal_class_name] = class_condition if classes.any?
|
74
|
-
@extended_query ||=
|
75
|
-
|
76
|
-
|
77
|
-
end
|
89
|
+
@extended_query ||= ThinkingSphinx::Search::Query.new(
|
90
|
+
context.search.query, conditions, options[:star]
|
91
|
+
).to_s
|
78
92
|
end
|
79
93
|
|
80
94
|
def group_attribute
|
81
|
-
options[:group_by]
|
95
|
+
options[:group_by].to_s if options[:group_by]
|
82
96
|
end
|
83
97
|
|
84
98
|
def group_order_clause
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
else
|
89
|
-
options[:order_group_by]
|
90
|
-
end
|
99
|
+
group_by = options[:order_group_by]
|
100
|
+
group_by = "#{group_by} ASC" if group_by.is_a? Symbol
|
101
|
+
group_by
|
91
102
|
end
|
92
103
|
|
93
104
|
def inclusive_filters
|
94
|
-
|
95
|
-
with[:sphinx_deleted] = false
|
96
|
-
end
|
105
|
+
(options[:with] || {}).merge({:sphinx_deleted => false})
|
97
106
|
end
|
98
107
|
|
99
108
|
def index_names
|
@@ -108,17 +117,13 @@ class ThinkingSphinx::Middlewares::SphinxQL <
|
|
108
117
|
@indices ||= ThinkingSphinx::IndexSet.new classes, options[:indices]
|
109
118
|
end
|
110
119
|
|
111
|
-
|
112
|
-
|
113
|
-
end
|
120
|
+
delegate :search, :to => :context
|
121
|
+
delegate :options, :to => :search
|
114
122
|
|
115
123
|
def order_clause
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
else
|
120
|
-
options[:order]
|
121
|
-
end
|
124
|
+
order_by = options[:order]
|
125
|
+
order_by = "#{order_by} ASC" if order_by.is_a? Symbol
|
126
|
+
order_by
|
122
127
|
end
|
123
128
|
|
124
129
|
def select_options
|
@@ -130,31 +135,96 @@ class ThinkingSphinx::Middlewares::SphinxQL <
|
|
130
135
|
end
|
131
136
|
end
|
132
137
|
|
133
|
-
|
134
|
-
|
138
|
+
delegate :configuration, :to => :context
|
139
|
+
delegate :settings, :to => :configuration
|
140
|
+
|
141
|
+
def values
|
142
|
+
options[:select] ||= '*, @groupby, @count' if group_attribute.present?
|
143
|
+
options[:select]
|
135
144
|
end
|
136
145
|
|
137
146
|
def statement
|
138
|
-
|
139
|
-
select.from *index_names.collect { |index| "`#{index}`" }
|
140
|
-
select.values values if values.present?
|
141
|
-
select.matching extended_query if extended_query.present?
|
142
|
-
select.where inclusive_filters if inclusive_filters.any?
|
143
|
-
select.where_all options[:with_all] if options[:with_all]
|
144
|
-
select.where_not exclusive_filters if exclusive_filters.any?
|
145
|
-
select.where_not_all options[:without_all] if options[:without_all]
|
146
|
-
select.order_by order_clause if order_clause.present?
|
147
|
-
select.group_by group_attribute if group_attribute.present?
|
148
|
-
select.order_within_group_by group_order_clause if group_order_clause.present?
|
149
|
-
select.offset context.search.offset
|
150
|
-
select.limit context.search.per_page
|
151
|
-
select.with_options select_options if select_options.keys.any?
|
152
|
-
end
|
147
|
+
Statement.new(self).to_riddle_query_select
|
153
148
|
end
|
154
149
|
|
155
|
-
|
156
|
-
|
157
|
-
|
150
|
+
class Statement
|
151
|
+
def initialize(report)
|
152
|
+
self.report = report
|
153
|
+
self.query = Riddle::Query::Select.new
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_riddle_query_select
|
157
|
+
filter_by_scopes
|
158
|
+
|
159
|
+
query
|
160
|
+
end
|
161
|
+
|
162
|
+
protected
|
163
|
+
attr_accessor :report, :query
|
164
|
+
def filter_by_scopes
|
165
|
+
scope_by_from
|
166
|
+
scope_by_values
|
167
|
+
scope_by_extended_query
|
168
|
+
scope_by_inclusive_filters
|
169
|
+
scope_by_with_all
|
170
|
+
scope_by_exclusive_filters
|
171
|
+
scope_by_without_all
|
172
|
+
|
173
|
+
scope_by_order
|
174
|
+
scope_by_group
|
175
|
+
scope_by_pagination
|
176
|
+
scope_by_options
|
177
|
+
end
|
178
|
+
|
179
|
+
def scope_by_from
|
180
|
+
query.from *(index_names.collect { |index| "`#{index}`" })
|
181
|
+
end
|
182
|
+
|
183
|
+
def scope_by_values
|
184
|
+
query.values values if values.present?
|
185
|
+
end
|
186
|
+
|
187
|
+
def scope_by_extended_query
|
188
|
+
query.matching extended_query if extended_query.present?
|
189
|
+
end
|
190
|
+
|
191
|
+
def scope_by_inclusive_filters
|
192
|
+
query.where inclusive_filters if inclusive_filters.any?
|
193
|
+
end
|
194
|
+
|
195
|
+
def scope_by_with_all
|
196
|
+
query.where_all options[:with_all] if options[:with_all]
|
197
|
+
end
|
198
|
+
|
199
|
+
def scope_by_exclusive_filters
|
200
|
+
query.where_not exclusive_filters if exclusive_filters.any?
|
201
|
+
end
|
202
|
+
|
203
|
+
def scope_by_without_all
|
204
|
+
query.where_not_all options[:without_all] if options[:without_all]
|
205
|
+
end
|
206
|
+
|
207
|
+
def scope_by_order
|
208
|
+
query.order_by order_clause if order_clause.present?
|
209
|
+
end
|
210
|
+
|
211
|
+
def scope_by_group
|
212
|
+
query.group_by group_attribute if group_attribute.present?
|
213
|
+
query.order_within_group_by group_order_clause if group_order_clause.present?
|
214
|
+
end
|
215
|
+
|
216
|
+
def scope_by_pagination
|
217
|
+
query.offset context.search.offset
|
218
|
+
query.limit context.search.per_page
|
219
|
+
end
|
220
|
+
|
221
|
+
def scope_by_options
|
222
|
+
query.with_options select_options if select_options.keys.any?
|
223
|
+
end
|
224
|
+
|
225
|
+
def method_missing(*args, &block)
|
226
|
+
report.send *args, &block
|
227
|
+
end
|
158
228
|
end
|
159
229
|
end
|
160
230
|
end
|
@@ -18,9 +18,13 @@ class ThinkingSphinx::Panes::ExcerptsPane
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def excerpt_words
|
21
|
-
@excerpt_words ||=
|
22
|
-
|
23
|
-
|
21
|
+
@excerpt_words ||= begin
|
22
|
+
conditions = @context.search.options[:conditions] || {}
|
23
|
+
ThinkingSphinx::Search::Query.new(
|
24
|
+
([@context.search.query] + conditions.values).compact.join(' '),
|
25
|
+
{}, @context.search.options[:star]
|
26
|
+
).to_s
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
class Excerpts
|
@@ -1,4 +1,13 @@
|
|
1
1
|
class ThinkingSphinx::RakeInterface
|
2
|
+
def clear
|
3
|
+
[
|
4
|
+
configuration.indices_location,
|
5
|
+
configuration.searchd.binlog_path
|
6
|
+
].each do |path|
|
7
|
+
FileUtils.rm_r(path) if File.exists?(path)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
2
11
|
def configure
|
3
12
|
puts "Generating configuration to #{configuration.configuration_file}"
|
4
13
|
configuration.render_to_file
|
@@ -37,6 +46,7 @@ class ThinkingSphinx::RakeInterface
|
|
37
46
|
def start
|
38
47
|
raise RuntimeError, 'searchd is already running' if controller.running?
|
39
48
|
|
49
|
+
FileUtils.mkdir_p configuration.indices_location
|
40
50
|
controller.start
|
41
51
|
|
42
52
|
if controller.running?
|
@@ -1,18 +1,22 @@
|
|
1
|
-
class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks
|
2
|
+
def initialize(reference, path = [])
|
3
|
+
@reference, @path = reference, path
|
4
|
+
end
|
5
5
|
|
6
|
-
def after_save
|
6
|
+
def after_save(instance)
|
7
7
|
return unless real_time_indices?
|
8
8
|
|
9
9
|
real_time_indices.each do |index|
|
10
|
-
|
10
|
+
objects_for(instance).each do |object|
|
11
|
+
ThinkingSphinx::RealTime::Transcriber.new(index).copy object
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
16
|
private
|
15
17
|
|
18
|
+
attr_reader :reference, :path
|
19
|
+
|
16
20
|
def configuration
|
17
21
|
ThinkingSphinx::Configuration.instance
|
18
22
|
end
|
@@ -21,6 +25,10 @@ class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks <
|
|
21
25
|
@indices ||= configuration.indices_for_references reference
|
22
26
|
end
|
23
27
|
|
28
|
+
def objects_for(instance)
|
29
|
+
Array(path.inject(instance) { |object, method| object.send method })
|
30
|
+
end
|
31
|
+
|
24
32
|
def real_time_indices?
|
25
33
|
real_time_indices.any?
|
26
34
|
end
|
@@ -30,8 +38,4 @@ class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks <
|
|
30
38
|
index.is_a? ThinkingSphinx::RealTime::Index
|
31
39
|
}
|
32
40
|
end
|
33
|
-
|
34
|
-
def reference
|
35
|
-
instance.class.name.underscore.to_sym
|
36
|
-
end
|
37
41
|
end
|
@@ -49,6 +49,25 @@ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
|
|
49
49
|
|
50
50
|
private
|
51
51
|
|
52
|
+
def append_unique_attribute(collection, attribute)
|
53
|
+
collection << attribute.name unless collection.include?(attribute.name)
|
54
|
+
end
|
55
|
+
|
56
|
+
def collection_for(attribute)
|
57
|
+
case attribute.type
|
58
|
+
when :integer, :boolean
|
59
|
+
attribute.multi? ? @rt_attr_multi : @rt_attr_uint
|
60
|
+
when :string
|
61
|
+
@rt_attr_string
|
62
|
+
when :timestamp
|
63
|
+
@rt_attr_timestamp
|
64
|
+
when :float
|
65
|
+
@rt_attr_float
|
66
|
+
else
|
67
|
+
raise "Unknown attribute type '#{attribute.type}'"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
52
71
|
def interpreter
|
53
72
|
ThinkingSphinx::RealTime::Interpreter
|
54
73
|
end
|
@@ -59,18 +78,7 @@ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
|
|
59
78
|
@rt_field = fields.collect &:name
|
60
79
|
|
61
80
|
attributes.each do |attribute|
|
62
|
-
|
63
|
-
when :integer, :boolean
|
64
|
-
@rt_attr_uint << attribute.name unless @rt_attr_uint.include?(attribute.name)
|
65
|
-
when :string
|
66
|
-
@rt_attr_string << attribute.name unless @rt_attr_string.include?(attribute.name)
|
67
|
-
when :timestamp
|
68
|
-
@rt_attr_timestamp << attribute.name unless @rt_attr_timestamp.include?(attribute.name)
|
69
|
-
when :float
|
70
|
-
@rt_attr_float << attribute.name unless @rt_attr_float.include?(attribute.name)
|
71
|
-
else
|
72
|
-
raise "Unknown attribute type '#{attribute.type}'"
|
73
|
-
end
|
81
|
+
append_unique_attribute collection_for(attribute), attribute
|
74
82
|
end
|
75
83
|
end
|
76
84
|
|