thinking-sphinx 2.0.6 → 2.0.7
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.
- data/HISTORY +157 -0
- data/lib/cucumber/thinking_sphinx/external_world.rb +12 -0
- data/lib/cucumber/thinking_sphinx/internal_world.rb +127 -0
- data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
- data/lib/thinking-sphinx.rb +1 -0
- data/lib/thinking_sphinx/action_controller.rb +31 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +53 -0
- data/lib/thinking_sphinx/active_record/collection_proxy.rb +40 -0
- data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +27 -0
- data/lib/thinking_sphinx/active_record/delta.rb +65 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +37 -0
- data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +21 -0
- data/lib/thinking_sphinx/active_record/log_subscriber.rb +61 -0
- data/lib/thinking_sphinx/active_record/scopes.rb +110 -0
- data/lib/thinking_sphinx/active_record.rb +383 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +87 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +62 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +171 -0
- data/lib/thinking_sphinx/association.rb +229 -0
- data/lib/thinking_sphinx/attribute.rb +407 -0
- data/lib/thinking_sphinx/auto_version.rb +38 -0
- data/lib/thinking_sphinx/bundled_search.rb +44 -0
- data/lib/thinking_sphinx/class_facet.rb +20 -0
- data/lib/thinking_sphinx/configuration.rb +335 -0
- data/lib/thinking_sphinx/context.rb +77 -0
- data/lib/thinking_sphinx/core/string.rb +15 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
- data/lib/thinking_sphinx/deltas.rb +28 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +99 -0
- data/lib/thinking_sphinx/excerpter.rb +23 -0
- data/lib/thinking_sphinx/facet.rb +128 -0
- data/lib/thinking_sphinx/facet_search.rb +170 -0
- data/lib/thinking_sphinx/field.rb +98 -0
- data/lib/thinking_sphinx/index/builder.rb +312 -0
- data/lib/thinking_sphinx/index/faux_column.rb +118 -0
- data/lib/thinking_sphinx/index.rb +157 -0
- data/lib/thinking_sphinx/join.rb +37 -0
- data/lib/thinking_sphinx/property.rb +185 -0
- data/lib/thinking_sphinx/railtie.rb +46 -0
- data/lib/thinking_sphinx/search.rb +995 -0
- data/lib/thinking_sphinx/search_methods.rb +439 -0
- data/lib/thinking_sphinx/sinatra.rb +7 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +51 -0
- data/lib/thinking_sphinx/source/sql.rb +157 -0
- data/lib/thinking_sphinx/source.rb +194 -0
- data/lib/thinking_sphinx/tasks.rb +132 -0
- data/lib/thinking_sphinx/test.rb +55 -0
- data/lib/thinking_sphinx/version.rb +3 -0
- data/lib/thinking_sphinx.rb +296 -0
- metadata +53 -4
@@ -0,0 +1,439 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module SearchMethods
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend ThinkingSphinx::SearchMethods::ClassMethods
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def search_context
|
11
|
+
# Comparing to name string to avoid class inheritance complications
|
12
|
+
case self.class.name
|
13
|
+
when 'Class'
|
14
|
+
self
|
15
|
+
else
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Searches through the Sphinx indexes for relevant matches. There's
|
21
|
+
# various ways to search, sort, group and filter - which are covered
|
22
|
+
# below.
|
23
|
+
#
|
24
|
+
# Also, if you have WillPaginate installed, the search method can be used
|
25
|
+
# just like paginate. The same parameters - :page and :per_page - work as
|
26
|
+
# expected, and the returned result set can be used by the will_paginate
|
27
|
+
# helper.
|
28
|
+
#
|
29
|
+
# == Basic Searching
|
30
|
+
#
|
31
|
+
# The simplest way of searching is straight text.
|
32
|
+
#
|
33
|
+
# ThinkingSphinx.search "pat"
|
34
|
+
# ThinkingSphinx.search "google"
|
35
|
+
# User.search "pat", :page => (params[:page] || 1)
|
36
|
+
# Article.search "relevant news issue of the day"
|
37
|
+
#
|
38
|
+
# If you specify :include, like in an #find call, this will be respected
|
39
|
+
# when loading the relevant models from the search results.
|
40
|
+
#
|
41
|
+
# User.search "pat", :include => :posts
|
42
|
+
#
|
43
|
+
# == Match Modes
|
44
|
+
#
|
45
|
+
# Sphinx supports 5 different matching modes. By default Thinking Sphinx
|
46
|
+
# uses :all, which unsurprisingly requires all the supplied search terms
|
47
|
+
# to match a result.
|
48
|
+
#
|
49
|
+
# Alternative modes include:
|
50
|
+
#
|
51
|
+
# User.search "pat allan", :match_mode => :any
|
52
|
+
# User.search "pat allan", :match_mode => :phrase
|
53
|
+
# User.search "pat | allan", :match_mode => :boolean
|
54
|
+
# User.search "@name pat | @username pat", :match_mode => :extended
|
55
|
+
#
|
56
|
+
# Any will find results with any of the search terms. Phrase treats the
|
57
|
+
# search terms a single phrase instead of individual words. Boolean and
|
58
|
+
# extended allow for more complex query syntax, refer to the sphinx
|
59
|
+
# documentation for further details.
|
60
|
+
#
|
61
|
+
# == Weighting
|
62
|
+
#
|
63
|
+
# Sphinx has support for weighting, where matches in one field can be
|
64
|
+
# considered more important than in another. Weights are integers, with 1
|
65
|
+
# as the default. They can be set per-search like this:
|
66
|
+
#
|
67
|
+
# User.search "pat allan", :field_weights => { :alias => 4, :aka => 2 }
|
68
|
+
#
|
69
|
+
# If you're searching multiple models, you can set per-index weights:
|
70
|
+
#
|
71
|
+
# ThinkingSphinx.search "pat", :index_weights => { User => 10 }
|
72
|
+
#
|
73
|
+
# See http://sphinxsearch.com/doc.html#weighting for further details.
|
74
|
+
#
|
75
|
+
# == Searching by Fields
|
76
|
+
#
|
77
|
+
# If you want to step it up a level, you can limit your search terms to
|
78
|
+
# specific fields:
|
79
|
+
#
|
80
|
+
# User.search :conditions => {:name => "pat"}
|
81
|
+
#
|
82
|
+
# This uses Sphinx's extended match mode, unless you specify a different
|
83
|
+
# match mode explicitly (but then this way of searching won't work). Also
|
84
|
+
# note that you don't need to put in a search string.
|
85
|
+
#
|
86
|
+
# == Searching by Attributes
|
87
|
+
#
|
88
|
+
# Also known as filters, you can limit your searches to documents that
|
89
|
+
# have specific values for their attributes. There are three ways to do
|
90
|
+
# this. The first two techniques work in all scenarios - using the :with
|
91
|
+
# or :with_all options.
|
92
|
+
#
|
93
|
+
# ThinkingSphinx.search :with => {:tag_ids => 10}
|
94
|
+
# ThinkingSphinx.search :with => {:tag_ids => [10,12]}
|
95
|
+
# ThinkingSphinx.search :with_all => {:tag_ids => [10,12]}
|
96
|
+
#
|
97
|
+
# The first :with search will match records with a tag_id attribute of 10.
|
98
|
+
# The second :with will match records with a tag_id attribute of 10 OR 12.
|
99
|
+
# If you need to find records that are tagged with ids 10 AND 12, you
|
100
|
+
# will need to use the :with_all search parameter. This is particuarly
|
101
|
+
# useful in conjunction with Multi Value Attributes (MVAs).
|
102
|
+
#
|
103
|
+
# The third filtering technique is only viable if you're searching with a
|
104
|
+
# specific model (not multi-model searching). With a single model,
|
105
|
+
# Thinking Sphinx can figure out what attributes and fields are available,
|
106
|
+
# so you can put it all in the :conditions hash, and it will sort it out.
|
107
|
+
#
|
108
|
+
# Node.search :conditions => {:parent_id => 10}
|
109
|
+
#
|
110
|
+
# Filters can be single values, arrays of values, or ranges.
|
111
|
+
#
|
112
|
+
# Article.search "East Timor", :conditions => {:rating => 3..5}
|
113
|
+
#
|
114
|
+
# == Excluding by Attributes
|
115
|
+
#
|
116
|
+
# Sphinx also supports negative filtering - where the filters are of
|
117
|
+
# attribute values to exclude. This is done with the :without option:
|
118
|
+
#
|
119
|
+
# User.search :without => {:role_id => 1}
|
120
|
+
#
|
121
|
+
# == Excluding by Primary Key
|
122
|
+
#
|
123
|
+
# There is a shortcut to exclude records by their ActiveRecord primary
|
124
|
+
# key:
|
125
|
+
#
|
126
|
+
# User.search :without_ids => 1
|
127
|
+
#
|
128
|
+
# Pass an array or a single value.
|
129
|
+
#
|
130
|
+
# The primary key must be an integer as a negative filter is used. Note
|
131
|
+
# that for multi-model search, an id may occur in more than one model.
|
132
|
+
#
|
133
|
+
# == Infix (Star) Searching
|
134
|
+
#
|
135
|
+
# Enable infix searching by something like this in config/sphinx.yml:
|
136
|
+
#
|
137
|
+
# development:
|
138
|
+
# enable_star: 1
|
139
|
+
# min_infix_len: 2
|
140
|
+
#
|
141
|
+
# Note that this will make indexing take longer.
|
142
|
+
#
|
143
|
+
# With those settings (and after reindexing), wildcard asterisks can be
|
144
|
+
# used in queries:
|
145
|
+
#
|
146
|
+
# Location.search "*elbourn*"
|
147
|
+
#
|
148
|
+
# To automatically add asterisks around every token (but not operators),
|
149
|
+
# pass the :star option:
|
150
|
+
#
|
151
|
+
# Location.search "elbourn -ustrali", :star => true,
|
152
|
+
# :match_mode => :boolean
|
153
|
+
#
|
154
|
+
# This would become "*elbourn* -*ustrali*". The :star option only adds the
|
155
|
+
# asterisks. You need to make the config/sphinx.yml changes yourself.
|
156
|
+
#
|
157
|
+
# By default, the tokens are assumed to match the regular expression
|
158
|
+
# /\w\+/u\+. If you've modified the charset_table, pass another regular
|
159
|
+
# expression, e.g.
|
160
|
+
#
|
161
|
+
# User.search("oo@bar.c", :star => /[\w@.]+/u)
|
162
|
+
#
|
163
|
+
# to search for "*oo@bar.c*" and not "*oo*@*bar*.*c*".
|
164
|
+
#
|
165
|
+
# == Sorting
|
166
|
+
#
|
167
|
+
# Sphinx can only sort by attributes, so generally you will need to avoid
|
168
|
+
# using field names in your :order option. However, if you're searching
|
169
|
+
# on a single model, and have specified some fields as sortable, you can
|
170
|
+
# use those field names and Thinking Sphinx will interpret accordingly.
|
171
|
+
# Remember: this will only happen for single-model searches, and only
|
172
|
+
# through the :order option.
|
173
|
+
#
|
174
|
+
# Location.search "Melbourne", :order => :state
|
175
|
+
# User.search :conditions => {:role_id => 2}, :order => "name ASC"
|
176
|
+
#
|
177
|
+
# Keep in mind that if you use a string, you *must* specify the direction
|
178
|
+
# (ASC or DESC) else Sphinx won't return any results. If you use a symbol
|
179
|
+
# then Thinking Sphinx assumes ASC, but if you wish to state otherwise,
|
180
|
+
# use the :sort_mode option:
|
181
|
+
#
|
182
|
+
# Location.search "Melbourne", :order => :state, :sort_mode => :desc
|
183
|
+
#
|
184
|
+
# Of course, there are other sort modes - check out the Sphinx
|
185
|
+
# documentation[http://sphinxsearch.com/doc.html] for that level of
|
186
|
+
# detail though.
|
187
|
+
#
|
188
|
+
# If desired, you can sort by a column in your model instead of a sphinx
|
189
|
+
# field or attribute. This sort only applies to the current page, so is
|
190
|
+
# most useful when performing a search with a single page of results.
|
191
|
+
#
|
192
|
+
# User.search("pat", :sql_order => "name")
|
193
|
+
#
|
194
|
+
# == Grouping
|
195
|
+
#
|
196
|
+
# For this you can use the group_by, group_clause and group_function
|
197
|
+
# options - which are all directly linked to Sphinx's expectations. No
|
198
|
+
# magic from Thinking Sphinx. It can get a little tricky, so make sure
|
199
|
+
# you read all the relevant
|
200
|
+
# documentation[http://sphinxsearch.com/doc.html#clustering] first.
|
201
|
+
#
|
202
|
+
# Grouping is done via three parameters within the options hash
|
203
|
+
# * <tt>:group_function</tt> determines the way grouping is done
|
204
|
+
# * <tt>:group_by</tt> determines the field which is used for grouping
|
205
|
+
# * <tt>:group_clause</tt> determines the sorting order
|
206
|
+
#
|
207
|
+
# As a convenience, you can also use
|
208
|
+
# * <tt>:group</tt>
|
209
|
+
# which sets :group_by and defaults to :group_function of :attr
|
210
|
+
#
|
211
|
+
# === group_function
|
212
|
+
#
|
213
|
+
# Valid values for :group_function are
|
214
|
+
# * <tt>:day</tt>, <tt>:week</tt>, <tt>:month</tt>, <tt>:year</tt> - Grouping is done by the respective timeframes.
|
215
|
+
# * <tt>:attr</tt>, <tt>:attrpair</tt> - Grouping is done by the specified attributes(s)
|
216
|
+
#
|
217
|
+
# === group_by
|
218
|
+
#
|
219
|
+
# This parameter denotes the field by which grouping is done. Note that
|
220
|
+
# the specified field must be a sphinx attribute or index.
|
221
|
+
#
|
222
|
+
# === group_clause
|
223
|
+
#
|
224
|
+
# This determines the sorting order of the groups. In a grouping search,
|
225
|
+
# the matches within a group will sorted by the <tt>:sort_mode</tt> and
|
226
|
+
# <tt>:order</tt> parameters. The group matches themselves however, will
|
227
|
+
# be sorted by <tt>:group_clause</tt>.
|
228
|
+
#
|
229
|
+
# The syntax for this is the same as an order parameter in extended sort
|
230
|
+
# mode. Namely, you can specify an SQL-like sort expression with up to 5
|
231
|
+
# attributes (including internal attributes), eg: "@relevance DESC, price
|
232
|
+
# ASC, @id DESC"
|
233
|
+
#
|
234
|
+
# === Grouping by timestamp
|
235
|
+
#
|
236
|
+
# Timestamp grouping groups off items by the day, week, month or year of
|
237
|
+
# the attribute given. In order to do this you need to define a timestamp
|
238
|
+
# attribute, which pretty much looks like the standard defintion for any
|
239
|
+
# attribute.
|
240
|
+
#
|
241
|
+
# define_index do
|
242
|
+
# #
|
243
|
+
# # All your other stuff
|
244
|
+
# #
|
245
|
+
# has :created_at
|
246
|
+
# end
|
247
|
+
#
|
248
|
+
# When you need to fire off your search, it'll go something to the tune of
|
249
|
+
#
|
250
|
+
# Fruit.search "apricot", :group_function => :day,
|
251
|
+
# :group_by => 'created_at'
|
252
|
+
#
|
253
|
+
# The <tt>@groupby</tt> special attribute will contain the date for that
|
254
|
+
# group. Depending on the <tt>:group_function</tt> parameter, the date
|
255
|
+
# format will be:
|
256
|
+
#
|
257
|
+
# * <tt>:day</tt> - YYYYMMDD
|
258
|
+
# * <tt>:week</tt> - YYYYNNN (NNN is the first day of the week in question,
|
259
|
+
# counting from the start of the year )
|
260
|
+
# * <tt>:month</tt> - YYYYMM
|
261
|
+
# * <tt>:year</tt> - YYYY
|
262
|
+
#
|
263
|
+
# === Grouping by attribute
|
264
|
+
#
|
265
|
+
# The syntax is the same as grouping by timestamp, except for the fact
|
266
|
+
# that the <tt>:group_function</tt> parameter is changed.
|
267
|
+
#
|
268
|
+
# Fruit.search "apricot", :group_function => :attr, :group_by => 'size'
|
269
|
+
#
|
270
|
+
# == Geo/Location Searching
|
271
|
+
#
|
272
|
+
# Sphinx - and therefore Thinking Sphinx - has the facility to search
|
273
|
+
# around a geographical point, using a given latitude and longitude. To
|
274
|
+
# take advantage of this, you will need to have both of those values in
|
275
|
+
# attributes. To search with that point, you can then use one of the
|
276
|
+
# following syntax examples:
|
277
|
+
#
|
278
|
+
# Address.search "Melbourne", :geo => [1.4, -2.217],
|
279
|
+
# :order => "@geodist asc"
|
280
|
+
# Address.search "Australia", :geo => [-0.55, 3.108],
|
281
|
+
# :order => "@geodist asc" :latitude_attr => "latit",
|
282
|
+
# :longitude_attr => "longit"
|
283
|
+
#
|
284
|
+
# The first example applies when your latitude and longitude attributes
|
285
|
+
# are named any of lat, latitude, lon, long or longitude. If that's not
|
286
|
+
# the case, you will need to explicitly state them in your search, _or_
|
287
|
+
# you can do so in your model:
|
288
|
+
#
|
289
|
+
# define_index do
|
290
|
+
# has :latit # Float column, stored in radians
|
291
|
+
# has :longit # Float column, stored in radians
|
292
|
+
#
|
293
|
+
# set_property :latitude_attr => "latit"
|
294
|
+
# set_property :longitude_attr => "longit"
|
295
|
+
# end
|
296
|
+
#
|
297
|
+
# Now, geo-location searching really only has an affect if you have a
|
298
|
+
# filter, sort or grouping clause related to it - otherwise it's just a
|
299
|
+
# normal search, and _will not_ return a distance value otherwise. To
|
300
|
+
# make use of the positioning difference, use the special attribute
|
301
|
+
# "@geodist" in any of your filters or sorting or grouping clauses.
|
302
|
+
#
|
303
|
+
# And don't forget - both the latitude and longitude you use in your
|
304
|
+
# search, and the values in your indexes, need to be stored as a float in
|
305
|
+
# radians, _not_ degrees. Keep in mind that if you do this conversion in
|
306
|
+
# SQL you will need to explicitly declare a column type of :float.
|
307
|
+
#
|
308
|
+
# define_index do
|
309
|
+
# has 'RADIANS(lat)', :as => :lat, :type => :float
|
310
|
+
# # ...
|
311
|
+
# end
|
312
|
+
#
|
313
|
+
# Once you've got your results set, you can access the distances as
|
314
|
+
# follows:
|
315
|
+
#
|
316
|
+
# @results.each_with_geodist do |result, distance|
|
317
|
+
# # ...
|
318
|
+
# end
|
319
|
+
#
|
320
|
+
# The distance value is returned as a float, representing the distance in
|
321
|
+
# metres.
|
322
|
+
#
|
323
|
+
# == Filtering by custom attributes
|
324
|
+
#
|
325
|
+
# Do note that this applies only to sphinx 0.9.9
|
326
|
+
#
|
327
|
+
# Should you find yourself in desperate need of a filter that involves
|
328
|
+
# selecting either one of multiple conditions, one solution could be
|
329
|
+
# provided by the :sphinx_select option within the search.
|
330
|
+
# This handles which fields are selected by sphinx from its store.
|
331
|
+
#
|
332
|
+
# The default value is "*", and you can add custom fields using syntax
|
333
|
+
# similar to sql:
|
334
|
+
#
|
335
|
+
# Flower.search "foo",
|
336
|
+
# :sphinx_select => "*, petals < 1 or color = 2 as grass"
|
337
|
+
#
|
338
|
+
# This will add the 'grass' attribute, which will now be usable in your
|
339
|
+
# filters.
|
340
|
+
#
|
341
|
+
# == Handling a Stale Index
|
342
|
+
#
|
343
|
+
# Especially if you don't use delta indexing, you risk having records in
|
344
|
+
# the Sphinx index that are no longer in the database. By default, those
|
345
|
+
# will simply come back as nils:
|
346
|
+
#
|
347
|
+
# >> pat_user.delete
|
348
|
+
# >> User.search("pat")
|
349
|
+
# Sphinx Result: [1,2]
|
350
|
+
# => [nil, <#User id: 2>]
|
351
|
+
#
|
352
|
+
# (If you search across multiple models, you'll get
|
353
|
+
# ActiveRecord::RecordNotFound.)
|
354
|
+
#
|
355
|
+
# You can simply Array#compact these results or handle the nils in some
|
356
|
+
# other way, but Sphinx will still report two results, and the missing
|
357
|
+
# records may upset your layout.
|
358
|
+
#
|
359
|
+
# If you pass :retry_stale => true to a single-model search, missing
|
360
|
+
# records will cause Thinking Sphinx to retry the query but excluding
|
361
|
+
# those records. Since search is paginated, the new search could
|
362
|
+
# potentially include missing records as well, so by default Thinking
|
363
|
+
# Sphinx will retry three times. Pass :retry_stale => 5 to retry five
|
364
|
+
# times, and so on. If there are still missing ids on the last retry, they
|
365
|
+
# are shown as nils.
|
366
|
+
#
|
367
|
+
def search(*args)
|
368
|
+
ThinkingSphinx::Search.new *search_options(args)
|
369
|
+
end
|
370
|
+
|
371
|
+
# Searches for results that match the parameters provided. Will only
|
372
|
+
# return the ids for the matching objects. See #search for syntax
|
373
|
+
# examples.
|
374
|
+
#
|
375
|
+
# Note that this only searches the Sphinx index, with no ActiveRecord
|
376
|
+
# queries. Thus, if your index is not in sync with the database, this
|
377
|
+
# method may return ids that no longer exist there.
|
378
|
+
#
|
379
|
+
def search_for_ids(*args)
|
380
|
+
ThinkingSphinx::Search.new *search_options(args, :ids_only => true)
|
381
|
+
end
|
382
|
+
|
383
|
+
# Checks if a document with the given id exists within a specific index.
|
384
|
+
# Expected parameters:
|
385
|
+
#
|
386
|
+
# - ID of the document
|
387
|
+
# - Index to check within
|
388
|
+
# - Options hash (defaults to {})
|
389
|
+
#
|
390
|
+
# Example:
|
391
|
+
#
|
392
|
+
# ThinkingSphinx.search_for_id(10, "user_core", :class => User)
|
393
|
+
#
|
394
|
+
def search_for_id(id, index, options = {})
|
395
|
+
ThinkingSphinx::Search.new(
|
396
|
+
*search_options([],
|
397
|
+
:ids_only => true,
|
398
|
+
:index => index,
|
399
|
+
:id_range => id..id
|
400
|
+
)
|
401
|
+
).any?
|
402
|
+
end
|
403
|
+
|
404
|
+
def count(*args)
|
405
|
+
search_context ? super : search_count(*args)
|
406
|
+
end
|
407
|
+
|
408
|
+
def search_count(*args)
|
409
|
+
search = ThinkingSphinx::Search.new(
|
410
|
+
*search_options(args, :ids_only => true)
|
411
|
+
)
|
412
|
+
search.first # forces the query
|
413
|
+
search.total_entries
|
414
|
+
end
|
415
|
+
|
416
|
+
# Model.facets *args
|
417
|
+
# ThinkingSphinx.facets *args
|
418
|
+
# ThinkingSphinx.facets *args, :all_facets => true
|
419
|
+
# ThinkingSphinx.facets *args, :class_facet => false
|
420
|
+
#
|
421
|
+
def facets(*args)
|
422
|
+
ThinkingSphinx::FacetSearch.new *search_options(args)
|
423
|
+
end
|
424
|
+
|
425
|
+
private
|
426
|
+
|
427
|
+
def search_options(args, options = {})
|
428
|
+
options = args.extract_options!.merge(options)
|
429
|
+
options[:classes] ||= classes_option
|
430
|
+
args << options
|
431
|
+
end
|
432
|
+
|
433
|
+
def classes_option
|
434
|
+
classes_option = [search_context].compact
|
435
|
+
classes_option.empty? ? nil : classes_option
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
class Source
|
3
|
+
module InternalProperties
|
4
|
+
def add_internal_attributes_and_facets
|
5
|
+
add_internal_attribute :sphinx_internal_id, nil,
|
6
|
+
@model.primary_key_for_sphinx.to_sym
|
7
|
+
add_internal_attribute :sphinx_deleted, :integer, "0"
|
8
|
+
add_internal_attribute :class_crc, :integer, crc_column, true
|
9
|
+
|
10
|
+
unless Riddle.loaded_version.to_i < 2
|
11
|
+
add_internal_attribute :sphinx_internal_class, :string, internal_class_column, true
|
12
|
+
add_internal_facet :sphinx_internal_class
|
13
|
+
else
|
14
|
+
add_internal_facet :class_crc
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_internal_attribute(name, type, contents, facet = false)
|
19
|
+
return unless attribute_by_alias(name).nil?
|
20
|
+
|
21
|
+
Attribute.new(self,
|
22
|
+
ThinkingSphinx::Index::FauxColumn.new(contents),
|
23
|
+
:type => type,
|
24
|
+
:as => name,
|
25
|
+
:facet => facet,
|
26
|
+
:admin => true
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_internal_facet(name)
|
31
|
+
return unless facet_by_alias(name).nil?
|
32
|
+
|
33
|
+
@model.sphinx_facets << ClassFacet.new(attribute_by_alias(name))
|
34
|
+
end
|
35
|
+
|
36
|
+
def attribute_by_alias(attr_alias)
|
37
|
+
@attributes.detect { |attrib| attrib.alias == attr_alias }
|
38
|
+
end
|
39
|
+
|
40
|
+
def facet_by_alias(name)
|
41
|
+
@model.sphinx_facets.detect { |facet| facet.name == name }
|
42
|
+
end
|
43
|
+
|
44
|
+
def subclasses_to_s
|
45
|
+
"'" + (@model.send(:descendants).collect { |klass|
|
46
|
+
klass.to_crc32.to_s
|
47
|
+
} << @model.to_crc32.to_s).join(",") + "'"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
class Source
|
3
|
+
module SQL
|
4
|
+
# Generates the big SQL statement to get the data back for all the fields
|
5
|
+
# and attributes, using all the relevant association joins. If you want
|
6
|
+
# the version filtered for delta values, send through :delta => true in
|
7
|
+
# the options. Won't do much though if the index isn't set up to support a
|
8
|
+
# delta sibling.
|
9
|
+
#
|
10
|
+
# Examples:
|
11
|
+
#
|
12
|
+
# source.to_sql
|
13
|
+
# source.to_sql(:delta => true)
|
14
|
+
#
|
15
|
+
def to_sql(options={})
|
16
|
+
relation = @model.unscoped
|
17
|
+
pre_select = 'SQL_NO_CACHE ' if adapter.sphinx_identifier == "mysql"
|
18
|
+
relation = relation.select(
|
19
|
+
pre_select.to_s + sql_select_clause(options[:offset])
|
20
|
+
)
|
21
|
+
|
22
|
+
all_associations.each do |assoc|
|
23
|
+
relation = relation.joins(assoc.arel_join)
|
24
|
+
end
|
25
|
+
|
26
|
+
relation = relation.where(sql_where_clause(options))
|
27
|
+
relation = relation.group(sql_group_clause)
|
28
|
+
relation = relation.order('NULL') if adapter.sphinx_identifier == "mysql"
|
29
|
+
relation.to_sql
|
30
|
+
end
|
31
|
+
|
32
|
+
# Simple helper method for the query range SQL - which is a statement that
|
33
|
+
# returns minimum and maximum id values. These can be filtered by delta -
|
34
|
+
# so pass in :delta => true to get the delta version of the SQL.
|
35
|
+
#
|
36
|
+
def to_sql_query_range(options={})
|
37
|
+
return nil if @index.options[:disable_range]
|
38
|
+
|
39
|
+
min_statement = adapter.convert_nulls(
|
40
|
+
"MIN(#{quote_column(@model.primary_key_for_sphinx)})", 1
|
41
|
+
)
|
42
|
+
max_statement = adapter.convert_nulls(
|
43
|
+
"MAX(#{quote_column(@model.primary_key_for_sphinx)})", 1
|
44
|
+
)
|
45
|
+
|
46
|
+
sql = "SELECT #{min_statement}, #{max_statement} " +
|
47
|
+
"FROM #{@model.quoted_table_name} "
|
48
|
+
if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
|
49
|
+
sql << "WHERE #{@index.delta_object.clause(@model, options[:delta])}"
|
50
|
+
end
|
51
|
+
|
52
|
+
sql
|
53
|
+
end
|
54
|
+
|
55
|
+
# Simple helper method for the query info SQL - which is a statement that
|
56
|
+
# returns the single row for a corresponding id.
|
57
|
+
#
|
58
|
+
def to_sql_query_info(offset)
|
59
|
+
"SELECT * FROM #{@model.quoted_table_name} WHERE " +
|
60
|
+
"#{quote_column(@model.primary_key_for_sphinx)} = (($id - #{offset}) / #{ThinkingSphinx.context.indexed_models.size})"
|
61
|
+
end
|
62
|
+
|
63
|
+
def sql_select_clause(offset)
|
64
|
+
unique_id_expr = ThinkingSphinx.unique_id_expression(adapter, offset)
|
65
|
+
|
66
|
+
(
|
67
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} #{unique_id_expr} AS #{quote_column(@model.primary_key_for_sphinx)} "] +
|
68
|
+
@fields.collect { |field| field.to_select_sql } +
|
69
|
+
@attributes.collect { |attribute| attribute.to_select_sql }
|
70
|
+
).compact.join(", ")
|
71
|
+
end
|
72
|
+
|
73
|
+
def sql_where_clause(options)
|
74
|
+
logic = []
|
75
|
+
logic += [
|
76
|
+
"#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} >= $start",
|
77
|
+
"#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} <= $end"
|
78
|
+
] unless @index.options[:disable_range]
|
79
|
+
|
80
|
+
if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
|
81
|
+
logic << "#{@index.delta_object.clause(@model, options[:delta])}"
|
82
|
+
end
|
83
|
+
|
84
|
+
logic += (@conditions || [])
|
85
|
+
logic.join(' AND ')
|
86
|
+
end
|
87
|
+
|
88
|
+
def sql_group_clause
|
89
|
+
internal_groupings = []
|
90
|
+
if @model.column_names.include?(@model.inheritance_column)
|
91
|
+
internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
|
92
|
+
end
|
93
|
+
|
94
|
+
(
|
95
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)}"] +
|
96
|
+
@fields.collect { |field| field.to_group_sql }.compact +
|
97
|
+
@attributes.collect { |attribute| attribute.to_group_sql }.compact +
|
98
|
+
@groupings + internal_groupings
|
99
|
+
).join(", ")
|
100
|
+
end
|
101
|
+
|
102
|
+
def sql_query_pre_for_core
|
103
|
+
if self.delta? && !@index.delta_object.reset_query(@model).blank?
|
104
|
+
[@index.delta_object.reset_query(@model)]
|
105
|
+
else
|
106
|
+
[]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def sql_query_pre_for_delta
|
111
|
+
[""]
|
112
|
+
end
|
113
|
+
|
114
|
+
def quote_column(column)
|
115
|
+
@model.connection.quote_column_name(column)
|
116
|
+
end
|
117
|
+
|
118
|
+
def crc_column
|
119
|
+
if @model.table_exists? &&
|
120
|
+
@model.column_names.include?(@model.inheritance_column)
|
121
|
+
|
122
|
+
types = types_to_crcs
|
123
|
+
return @model.to_crc32.to_s if types.empty?
|
124
|
+
|
125
|
+
adapter.case(adapter.convert_nulls(
|
126
|
+
adapter.quote_with_table(@model.inheritance_column)),
|
127
|
+
types, @model.to_crc32)
|
128
|
+
else
|
129
|
+
@model.to_crc32.to_s
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def internal_class_column
|
134
|
+
if @model.table_exists? &&
|
135
|
+
@model.column_names.include?(@model.inheritance_column)
|
136
|
+
adapter.quote_with_table(@model.inheritance_column)
|
137
|
+
else
|
138
|
+
"'#{@model.name}'"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def type_values
|
143
|
+
@model.connection.select_values <<-SQL
|
144
|
+
SELECT DISTINCT #{@model.inheritance_column}
|
145
|
+
FROM #{@model.table_name}
|
146
|
+
SQL
|
147
|
+
end
|
148
|
+
|
149
|
+
def types_to_crcs
|
150
|
+
type_values.compact.inject({}) { |hash, type|
|
151
|
+
hash[type] = type.to_crc32
|
152
|
+
hash
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|