thinking-sphinx 3.0.3 → 3.0.4
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 +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
|
|