freelancing-god-thinking-sphinx 1.1.5 → 1.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README +14 -0
- data/lib/thinking_sphinx.rb +17 -2
- data/lib/thinking_sphinx/active_record.rb +19 -4
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +1 -1
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +1 -1
- data/lib/thinking_sphinx/association.rb +17 -0
- data/lib/thinking_sphinx/attribute.rb +93 -6
- data/lib/thinking_sphinx/collection.rb +6 -1
- data/lib/thinking_sphinx/core/string.rb +3 -10
- data/lib/thinking_sphinx/deltas.rb +7 -2
- data/lib/thinking_sphinx/deltas/default_delta.rb +6 -4
- data/lib/thinking_sphinx/facet.rb +1 -1
- data/lib/thinking_sphinx/index.rb +8 -8
- data/lib/thinking_sphinx/index/builder.rb +12 -2
- data/lib/thinking_sphinx/search.rb +29 -12
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +2 -2
- data/spec/unit/thinking_sphinx/attribute_spec.rb +15 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +90 -0
- metadata +2 -2
data/README
CHANGED
@@ -10,6 +10,8 @@ Keep in mind that while Thinking Sphinx works for ActiveRecord with Merb, it doe
|
|
10
10
|
|
11
11
|
Fork on GitHub and after you've committed tested patches, send a pull request.
|
12
12
|
|
13
|
+
To quickly see if your system is ready to run the thinking sphinx specs, run the contribute.rb script found in the project root directory. Use the following instructions to install any missing requirements.
|
14
|
+
|
13
15
|
To get the spec suite running, you will need to install the not-a-mock gem if you don't already have it:
|
14
16
|
|
15
17
|
git clone git://github.com/freelancing-god/not-a-mock.git
|
@@ -24,9 +26,14 @@ Then install the ginger gem. The steps are the same, except that you might need
|
|
24
26
|
rake gem
|
25
27
|
sudo gem install pkg/ginger-1.1.0.gem
|
26
28
|
|
29
|
+
Then install the faker gem:
|
30
|
+
|
31
|
+
sudo gem install faker
|
32
|
+
|
27
33
|
Then set up your database:
|
28
34
|
|
29
35
|
cp spec/fixtures/database.yml.default spec/fixtures/database.yml
|
36
|
+
cp spec/fixtures/database.yml.default features/support/db/database.yml
|
30
37
|
mysqladmin -u root create thinking_sphinx
|
31
38
|
|
32
39
|
Make sure you don't have another Sphinx daemon (searchd) running. If you do, quit it with "rake ts:stop"
|
@@ -35,10 +42,12 @@ in the app root.
|
|
35
42
|
You should now have a passing test suite from which to build your patch on.
|
36
43
|
|
37
44
|
rake spec
|
45
|
+
rake features (Note: You will need MySQL and Postgres gems to run the full suite. You may want to tweak your rakefile)
|
38
46
|
|
39
47
|
If you get the message "Failed to start searchd daemon", run the spec with sudo:
|
40
48
|
|
41
49
|
sudo rake spec
|
50
|
+
sudo rake features
|
42
51
|
|
43
52
|
If you quit the spec suite before it's completed, you may be left with data in the test
|
44
53
|
database, causing the next run to have failures. Let that run complete and then try again.
|
@@ -106,3 +115,8 @@ Since I first released this library, there's been quite a few people who have su
|
|
106
115
|
- Dan Pickett
|
107
116
|
- Alex Caudill
|
108
117
|
- Jim Benton
|
118
|
+
- John Aughey
|
119
|
+
- Keith Pitty
|
120
|
+
- Jeff Talbot
|
121
|
+
- Dana Contreras
|
122
|
+
- Menno van der Sman
|
data/lib/thinking_sphinx.rb
CHANGED
@@ -34,7 +34,7 @@ module ThinkingSphinx
|
|
34
34
|
module Version #:nodoc:
|
35
35
|
Major = 1
|
36
36
|
Minor = 1
|
37
|
-
Tiny =
|
37
|
+
Tiny = 6
|
38
38
|
|
39
39
|
String = [Major, Minor, Tiny].join('.')
|
40
40
|
end
|
@@ -60,6 +60,10 @@ module ThinkingSphinx
|
|
60
60
|
@@indexed_models ||= []
|
61
61
|
end
|
62
62
|
|
63
|
+
def self.unique_id_expression(offset = nil)
|
64
|
+
"* #{ThinkingSphinx.indexed_models.size} + #{offset || 0}"
|
65
|
+
end
|
66
|
+
|
63
67
|
# Check if index definition is disabled.
|
64
68
|
#
|
65
69
|
def self.define_indexes?
|
@@ -134,11 +138,22 @@ module ThinkingSphinx
|
|
134
138
|
end
|
135
139
|
|
136
140
|
def self.sphinx_running?
|
137
|
-
!!sphinx_pid
|
141
|
+
!!sphinx_pid && pid_active?(sphinx_pid)
|
138
142
|
end
|
139
143
|
|
140
144
|
def self.sphinx_pid
|
141
145
|
pid_file = ThinkingSphinx::Configuration.instance.pid_file
|
142
146
|
`cat #{pid_file}`[/\d+/] if File.exists?(pid_file)
|
143
147
|
end
|
148
|
+
|
149
|
+
def self.pid_active?(pid)
|
150
|
+
return true if RUBY_PLATFORM =~ /mswin/
|
151
|
+
|
152
|
+
begin
|
153
|
+
Process.getpgid(pid.to_i)
|
154
|
+
true
|
155
|
+
rescue Exception => e
|
156
|
+
false
|
157
|
+
end
|
158
|
+
end
|
144
159
|
end
|
@@ -207,11 +207,20 @@ module ThinkingSphinx
|
|
207
207
|
)
|
208
208
|
end
|
209
209
|
|
210
|
+
def in_index?(suffix)
|
211
|
+
self.class.search_for_id self.sphinx_document_id, sphinx_index_name(suffix)
|
212
|
+
end
|
213
|
+
|
210
214
|
def in_core_index?
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
+
in_index? "core"
|
216
|
+
end
|
217
|
+
|
218
|
+
def in_delta_index?
|
219
|
+
in_index? "delta"
|
220
|
+
end
|
221
|
+
|
222
|
+
def in_both_indexes?
|
223
|
+
in_core_index? && in_delta_index?
|
215
224
|
end
|
216
225
|
|
217
226
|
def toggle_deleted
|
@@ -241,5 +250,11 @@ module ThinkingSphinx
|
|
241
250
|
(self.id * ThinkingSphinx.indexed_models.size) +
|
242
251
|
ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
|
243
252
|
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def sphinx_index_name(suffix)
|
257
|
+
"#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
|
258
|
+
end
|
244
259
|
end
|
245
260
|
end
|
@@ -17,7 +17,7 @@ module ThinkingSphinx
|
|
17
17
|
when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
|
18
18
|
ThinkingSphinx::PostgreSQLAdapter.new model
|
19
19
|
else
|
20
|
-
raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
|
20
|
+
raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{model.connection.class.name}"
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -99,6 +99,23 @@ module ThinkingSphinx
|
|
99
99
|
@reflection.klass.column_names.include?(column.to_s)
|
100
100
|
end
|
101
101
|
|
102
|
+
def primary_key_from_reflection
|
103
|
+
if @reflection.options[:through]
|
104
|
+
@reflection.source_reflection.options[:foreign_key] ||
|
105
|
+
@reflection.source_reflection.primary_key_name
|
106
|
+
else
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def table
|
112
|
+
if @reflection.options[:through]
|
113
|
+
@join.aliased_join_table_name
|
114
|
+
else
|
115
|
+
@join.aliased_table_name
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
102
119
|
private
|
103
120
|
|
104
121
|
# Returns all the objects that could be currently instantiated from a
|
@@ -9,14 +9,15 @@ module ThinkingSphinx
|
|
9
9
|
# associations. Which can get messy. Use Index.link!, it really helps.
|
10
10
|
#
|
11
11
|
class Attribute
|
12
|
-
attr_accessor :alias, :columns, :associations, :model, :faceted
|
12
|
+
attr_accessor :alias, :columns, :associations, :model, :faceted, :source
|
13
13
|
|
14
14
|
# To create a new attribute, you'll need to pass in either a single Column
|
15
15
|
# or an array of them, and some (optional) options.
|
16
16
|
#
|
17
17
|
# Valid options are:
|
18
|
-
# - :as
|
19
|
-
# - :type
|
18
|
+
# - :as => :alias_name
|
19
|
+
# - :type => :attribute_type
|
20
|
+
# - :source => :field, :query, :ranged_query
|
20
21
|
#
|
21
22
|
# Alias is only required in three circumstances: when there's
|
22
23
|
# another attribute or field with the same name, when the column name is
|
@@ -28,6 +29,13 @@ module ThinkingSphinx
|
|
28
29
|
# can't be figured out by the column - ie: when not actually using a
|
29
30
|
# database column as your source.
|
30
31
|
#
|
32
|
+
# Source is only used for multi-value attributes (MVA). By default this will
|
33
|
+
# use a left-join and a group_concat to obtain the values. For better performance
|
34
|
+
# during indexing it can be beneficial to let Sphinx use a separate query to retrieve
|
35
|
+
# all document,value-pairs.
|
36
|
+
# Either :query or :ranged_query will enable this feature, where :ranged_query will cause
|
37
|
+
# the query to be executed incremental.
|
38
|
+
#
|
31
39
|
# Example usage:
|
32
40
|
#
|
33
41
|
# Attribute.new(
|
@@ -40,6 +48,12 @@ module ThinkingSphinx
|
|
40
48
|
# )
|
41
49
|
#
|
42
50
|
# Attribute.new(
|
51
|
+
# Column.new(:posts, :id),
|
52
|
+
# :as => :post_ids,
|
53
|
+
# :source => :ranged_query
|
54
|
+
# )
|
55
|
+
#
|
56
|
+
# Attribute.new(
|
43
57
|
# [Column.new(:pages, :id), Column.new(:articles, :id)],
|
44
58
|
# :as => :content_ids
|
45
59
|
# )
|
@@ -62,6 +76,9 @@ module ThinkingSphinx
|
|
62
76
|
@alias = options[:as]
|
63
77
|
@type = options[:type]
|
64
78
|
@faceted = options[:facet]
|
79
|
+
@source = options[:source]
|
80
|
+
|
81
|
+
@type ||= :multi unless @source.nil?
|
65
82
|
end
|
66
83
|
|
67
84
|
# Get the part of the SELECT clause related to this attribute. Don't forget
|
@@ -71,6 +88,8 @@ module ThinkingSphinx
|
|
71
88
|
# datetimes to timestamps, as needed.
|
72
89
|
#
|
73
90
|
def to_select_sql
|
91
|
+
return nil unless include_as_association?
|
92
|
+
|
74
93
|
clause = @columns.collect { |column|
|
75
94
|
column_with_prefix(column)
|
76
95
|
}.join(', ')
|
@@ -113,9 +132,20 @@ module ThinkingSphinx
|
|
113
132
|
}[type]
|
114
133
|
end
|
115
134
|
|
116
|
-
def
|
135
|
+
def include_as_association?
|
136
|
+
! (type == :multi && (source == :query || source == :ranged_query))
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns the configuration value that should be used for
|
140
|
+
# the attribute.
|
141
|
+
# Special case is the multi-valued attribute that needs some
|
142
|
+
# extra configuration.
|
143
|
+
#
|
144
|
+
def config_value(offset = nil)
|
117
145
|
if type == :multi
|
118
|
-
|
146
|
+
multi_config = include_as_association? ? "field" :
|
147
|
+
source_value(offset).gsub(/\n\s*/, " ")
|
148
|
+
"uint #{unique_name} from #{multi_config}"
|
119
149
|
else
|
120
150
|
unique_name
|
121
151
|
end
|
@@ -157,14 +187,71 @@ module ThinkingSphinx
|
|
157
187
|
|
158
188
|
private
|
159
189
|
|
190
|
+
def source_value(offset)
|
191
|
+
if is_string?
|
192
|
+
"#{source.to_s.dasherize}; #{columns.first.__name}"
|
193
|
+
elsif source == :ranged_query
|
194
|
+
"ranged-query; #{query offset} #{query_clause}; #{range_query}"
|
195
|
+
else
|
196
|
+
"query; #{query offset}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def query(offset)
|
201
|
+
assoc = association_for_mva
|
202
|
+
raise "Could not determine SQL for MVA" if assoc.nil?
|
203
|
+
|
204
|
+
<<-SQL
|
205
|
+
SELECT #{foreign_key_for_mva assoc}
|
206
|
+
#{ThinkingSphinx.unique_id_expression(offset)} AS #{quote_column('id')},
|
207
|
+
#{primary_key_for_mva(assoc)} AS #{quote_column(unique_name)}
|
208
|
+
FROM #{quote_table_name assoc.table}
|
209
|
+
SQL
|
210
|
+
end
|
211
|
+
|
212
|
+
def query_clause
|
213
|
+
foreign_key = foreign_key_for_mva association_for_mva
|
214
|
+
"WHERE #{foreign_key} >= $start AND #{foreign_key} <= $end"
|
215
|
+
end
|
216
|
+
|
217
|
+
def range_query
|
218
|
+
assoc = association_for_mva
|
219
|
+
foreign_key = foreign_key_for_mva assoc
|
220
|
+
"SELECT MIN(#{foreign_key}), MAX(#{foreign_key}) FROM #{quote_table_name assoc.table}"
|
221
|
+
end
|
222
|
+
|
223
|
+
def primary_key_for_mva(assoc)
|
224
|
+
quote_with_table(
|
225
|
+
assoc.table, assoc.primary_key_from_reflection || columns.first.__name
|
226
|
+
)
|
227
|
+
end
|
228
|
+
|
229
|
+
def foreign_key_for_mva(assoc)
|
230
|
+
quote_with_table assoc.table, assoc.reflection.primary_key_name
|
231
|
+
end
|
232
|
+
|
233
|
+
def association_for_mva
|
234
|
+
@association_for_mva ||= associations[columns.first].detect { |assoc|
|
235
|
+
assoc.has_column?(columns.first.__name)
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
160
239
|
def adapter
|
161
240
|
@adapter ||= @model.sphinx_database_adapter
|
162
241
|
end
|
163
242
|
|
243
|
+
def quote_with_table(table, column)
|
244
|
+
"#{quote_table_name(table)}.#{quote_column(column)}"
|
245
|
+
end
|
246
|
+
|
164
247
|
def quote_column(column)
|
165
248
|
@model.connection.quote_column_name(column)
|
166
249
|
end
|
167
250
|
|
251
|
+
def quote_table_name(table_name)
|
252
|
+
@model.connection.quote_table_name(table_name)
|
253
|
+
end
|
254
|
+
|
168
255
|
# Indication of whether the columns should be concatenated with a space
|
169
256
|
# between each value. True if there's either multiple sources or multiple
|
170
257
|
# associations.
|
@@ -192,7 +279,7 @@ module ThinkingSphinx
|
|
192
279
|
else
|
193
280
|
associations[column].collect { |assoc|
|
194
281
|
assoc.has_column?(column.__name) ?
|
195
|
-
"#{
|
282
|
+
"#{quote_table_name(assoc.join.aliased_table_name)}" +
|
196
283
|
".#{quote_column(column.__name)}" :
|
197
284
|
nil
|
198
285
|
}.compact.join(', ')
|
@@ -48,7 +48,8 @@ module ThinkingSphinx
|
|
48
48
|
:all,
|
49
49
|
:conditions => {klass.primary_key.to_sym => ids},
|
50
50
|
:include => (options[:include] || index_options[:include]),
|
51
|
-
:select => (options[:select] || index_options[:select])
|
51
|
+
:select => (options[:select] || index_options[:select]),
|
52
|
+
:order => (options[:sql_order] || index_options[:sql_order])
|
52
53
|
) : []
|
53
54
|
|
54
55
|
# Raise an exception if we find records in Sphinx but not in the DB, so
|
@@ -59,6 +60,10 @@ module ThinkingSphinx
|
|
59
60
|
raise StaleIdsException, stale_ids
|
60
61
|
end
|
61
62
|
|
63
|
+
# if the user has specified an SQL order, return the collection
|
64
|
+
# without rearranging it into the Sphinx order
|
65
|
+
return instances if options[:sql_order]
|
66
|
+
|
62
67
|
ids.collect { |obj_id|
|
63
68
|
instances.detect { |obj| obj.id == obj_id }
|
64
69
|
}
|
@@ -1,18 +1,11 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
1
3
|
module ThinkingSphinx
|
2
4
|
module Core
|
3
5
|
module String
|
4
|
-
|
5
6
|
def to_crc32
|
6
|
-
|
7
|
-
self.each_byte do |byte|
|
8
|
-
result ^= byte
|
9
|
-
8.times do
|
10
|
-
result = (result >> 1) ^ (0xEDB88320 * (result & 1))
|
11
|
-
end
|
12
|
-
end
|
13
|
-
result ^ 0xFFFFFFFF
|
7
|
+
Zlib.crc32 self
|
14
8
|
end
|
15
|
-
|
16
9
|
end
|
17
10
|
end
|
18
11
|
end
|
@@ -5,7 +5,8 @@ require 'thinking_sphinx/deltas/datetime_delta'
|
|
5
5
|
module ThinkingSphinx
|
6
6
|
module Deltas
|
7
7
|
def self.parse(index, options)
|
8
|
-
|
8
|
+
delta_option = options.delete(:delta)
|
9
|
+
case delta_option
|
9
10
|
when TrueClass, :default
|
10
11
|
DefaultDelta.new index, options
|
11
12
|
when :delayed
|
@@ -15,7 +16,11 @@ module ThinkingSphinx
|
|
15
16
|
when FalseClass, nil
|
16
17
|
nil
|
17
18
|
else
|
18
|
-
|
19
|
+
if delta_option.ancestors.include?(ThinkingSphinx::Deltas::DefaultDelta)
|
20
|
+
delta_option.new index, options
|
21
|
+
else
|
22
|
+
raise "Unknown delta type"
|
23
|
+
end
|
19
24
|
end
|
20
25
|
end
|
21
26
|
end
|
@@ -11,18 +11,20 @@ module ThinkingSphinx
|
|
11
11
|
def index(model, instance = nil)
|
12
12
|
return true unless ThinkingSphinx.updates_enabled? &&
|
13
13
|
ThinkingSphinx.deltas_enabled?
|
14
|
+
return true if instance && !toggled(instance)
|
14
15
|
|
15
16
|
config = ThinkingSphinx::Configuration.instance
|
16
17
|
client = Riddle::Client.new config.address, config.port
|
18
|
+
rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
|
19
|
+
|
20
|
+
output = `#{config.bin_path}indexer --config #{config.config_file} #{rotate} #{delta_index_name model}`
|
21
|
+
puts(output) unless ThinkingSphinx.suppress_delta_output?
|
17
22
|
|
18
23
|
client.update(
|
19
24
|
core_index_name(model),
|
20
25
|
['sphinx_deleted'],
|
21
26
|
{instance.sphinx_document_id => [1]}
|
22
|
-
) if instance && ThinkingSphinx.sphinx_running? && instance.
|
23
|
-
|
24
|
-
output = `#{config.bin_path}indexer --config #{config.config_file} --rotate #{delta_index_name model}`
|
25
|
-
puts output unless ThinkingSphinx.suppress_delta_output?
|
27
|
+
) if instance && ThinkingSphinx.sphinx_running? && instance.in_both_indexes?
|
26
28
|
|
27
29
|
true
|
28
30
|
end
|
@@ -56,7 +56,7 @@ module ThinkingSphinx
|
|
56
56
|
)
|
57
57
|
|
58
58
|
set_source_database_settings source
|
59
|
-
set_source_attributes source
|
59
|
+
set_source_attributes source, offset
|
60
60
|
set_source_sql source, offset
|
61
61
|
set_source_settings source
|
62
62
|
|
@@ -73,7 +73,7 @@ module ThinkingSphinx
|
|
73
73
|
source.parent = "#{name}_core_#{index}"
|
74
74
|
|
75
75
|
set_source_database_settings source
|
76
|
-
set_source_attributes source
|
76
|
+
set_source_attributes source, offset
|
77
77
|
set_source_sql source, offset, true
|
78
78
|
|
79
79
|
source
|
@@ -200,8 +200,8 @@ module ThinkingSphinx
|
|
200
200
|
}.flatten +
|
201
201
|
# attribute associations
|
202
202
|
@attributes.collect { |attrib|
|
203
|
-
attrib.associations.values
|
204
|
-
}.flatten
|
203
|
+
attrib.associations.values if attrib.include_as_association?
|
204
|
+
}.compact.flatten
|
205
205
|
).uniq.collect { |assoc|
|
206
206
|
# get ancestors as well as column-level associations
|
207
207
|
assoc.ancestors
|
@@ -285,9 +285,9 @@ module ThinkingSphinx
|
|
285
285
|
source.sql_sock = config[:socket]
|
286
286
|
end
|
287
287
|
|
288
|
-
def set_source_attributes(source)
|
288
|
+
def set_source_attributes(source, offset = nil)
|
289
289
|
attributes.each do |attrib|
|
290
|
-
source.send(attrib.type_to_config) << attrib.config_value
|
290
|
+
source.send(attrib.type_to_config) << attrib.config_value(offset)
|
291
291
|
end
|
292
292
|
end
|
293
293
|
|
@@ -354,14 +354,14 @@ module ThinkingSphinx
|
|
354
354
|
internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
|
355
355
|
end
|
356
356
|
|
357
|
-
unique_id_expr =
|
357
|
+
unique_id_expr = ThinkingSphinx.unique_id_expression(options[:offset])
|
358
358
|
|
359
359
|
sql = <<-SQL
|
360
360
|
SELECT #{ (
|
361
361
|
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
|
362
362
|
@fields.collect { |field| field.to_select_sql } +
|
363
363
|
@attributes.collect { |attribute| attribute.to_select_sql }
|
364
|
-
).join(", ") }
|
364
|
+
).compact.join(", ") }
|
365
365
|
FROM #{ @model.table_name }
|
366
366
|
#{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
|
367
367
|
WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
|
@@ -200,7 +200,17 @@ module ThinkingSphinx
|
|
200
200
|
#
|
201
201
|
# Please don't forget to add a boolean field named 'delta' to your
|
202
202
|
# model's database table if enabling the delta index for it.
|
203
|
+
# Valid options for the delta property are:
|
203
204
|
#
|
205
|
+
# true
|
206
|
+
# false
|
207
|
+
# :default
|
208
|
+
# :delayed
|
209
|
+
# :datetime
|
210
|
+
#
|
211
|
+
# You can also extend ThinkingSphinx::Deltas::DefaultDelta to implement
|
212
|
+
# your own handling for delta indexing.
|
213
|
+
|
204
214
|
def set_property(*args)
|
205
215
|
options = args.extract_options!
|
206
216
|
if options.empty?
|
@@ -224,8 +234,8 @@ module ThinkingSphinx
|
|
224
234
|
#
|
225
235
|
# Example: indexes assoc(:properties).column
|
226
236
|
#
|
227
|
-
def assoc(assoc)
|
228
|
-
FauxColumn.new(
|
237
|
+
def assoc(assoc, *args)
|
238
|
+
FauxColumn.new(assoc, *args)
|
229
239
|
end
|
230
240
|
end
|
231
241
|
end
|
@@ -94,16 +94,24 @@ module ThinkingSphinx
|
|
94
94
|
# == Searching by Attributes
|
95
95
|
#
|
96
96
|
# Also known as filters, you can limit your searches to documents that
|
97
|
-
# have specific values for their attributes. There are
|
98
|
-
# this. The first
|
99
|
-
#
|
100
|
-
#
|
101
|
-
# ThinkingSphinx::Search.search :with => {:
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
97
|
+
# have specific values for their attributes. There are three ways to do
|
98
|
+
# this. The first two techniques work in all scenarios - using the :with
|
99
|
+
# or :with_all options.
|
100
|
+
#
|
101
|
+
# ThinkingSphinx::Search.search :with => {:tag_ids => 10}
|
102
|
+
# ThinkingSphinx::Search.search :with => {:tag_ids => [10,12]}
|
103
|
+
# ThinkingSphinx::Search.search :with_all => {:tag_ids => [10,12]}
|
104
|
+
#
|
105
|
+
# The first :with search will match records with a tag_id attribute of 10.
|
106
|
+
# The second :with will match records with a tag_id attribute of 10 OR 12.
|
107
|
+
# If you need to find records that are tagged with ids 10 AND 12, you
|
108
|
+
# will need to use the :with_all search parameter. This is particuarly
|
109
|
+
# useful in conjunction with Multi Value Attributes (MVAs).
|
110
|
+
#
|
111
|
+
# The third filtering technique is only viable if you're searching with a
|
112
|
+
# specific model (not multi-model searching). With a single model,
|
113
|
+
# Thinking Sphinx can figure out what attributes and fields are available,
|
114
|
+
# so you can put it all in the :conditions hash, and it will sort it out.
|
107
115
|
#
|
108
116
|
# Node.search :conditions => {:parent_id => 10}
|
109
117
|
#
|
@@ -186,6 +194,12 @@ module ThinkingSphinx
|
|
186
194
|
# documentation[http://sphinxsearch.com/doc.html] for that level of
|
187
195
|
# detail though.
|
188
196
|
#
|
197
|
+
# If desired, you can sort by a column in your model instead of a sphinx
|
198
|
+
# field or attribute. This sort only applies to the current page, so is
|
199
|
+
# most useful when performing a search with a single page of results.
|
200
|
+
#
|
201
|
+
# User.search("pat", :sql_order => "name")
|
202
|
+
#
|
189
203
|
# == Grouping
|
190
204
|
#
|
191
205
|
# For this you can use the group_by, group_clause and group_function
|
@@ -291,8 +305,10 @@ module ThinkingSphinx
|
|
291
305
|
def retry_search_on_stale_index(query, options, &block)
|
292
306
|
stale_ids = []
|
293
307
|
stale_retries_left = case options[:retry_stale]
|
294
|
-
when true
|
295
|
-
|
308
|
+
when true
|
309
|
+
3 # default to three retries
|
310
|
+
when nil, false
|
311
|
+
0 # no retries
|
296
312
|
else options[:retry_stale].to_i
|
297
313
|
end
|
298
314
|
begin
|
@@ -389,6 +405,7 @@ module ThinkingSphinx
|
|
389
405
|
|
390
406
|
client.limit = options[:per_page].to_i if options[:per_page]
|
391
407
|
page = options[:page] ? options[:page].to_i : 1
|
408
|
+
page = 1 if page <= 0
|
392
409
|
client.offset = (page - 1) * client.limit
|
393
410
|
|
394
411
|
begin
|
@@ -76,7 +76,7 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
|
|
76
76
|
|
77
77
|
@person = Person.new
|
78
78
|
@person.stub_method(
|
79
|
-
:
|
79
|
+
:in_both_indexes? => false,
|
80
80
|
:sphinx_document_id => 1
|
81
81
|
)
|
82
82
|
|
@@ -126,7 +126,7 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
|
|
126
126
|
end
|
127
127
|
|
128
128
|
it "should update the deleted attribute if in the core index" do
|
129
|
-
@person.stub_method(:
|
129
|
+
@person.stub_method(:in_both_indexes? => true)
|
130
130
|
|
131
131
|
@person.send(:index_delta)
|
132
132
|
|
@@ -209,4 +209,19 @@ describe ThinkingSphinx::Attribute do
|
|
209
209
|
attribute.send(:all_ints?).should be_false
|
210
210
|
end
|
211
211
|
end
|
212
|
+
|
213
|
+
describe "with custom queries" do
|
214
|
+
before :each do
|
215
|
+
index = CricketTeam.sphinx_indexes.first
|
216
|
+
@statement = index.to_riddle_for_core(0, 0).sql_attr_multi.first
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should track the query type accordingly" do
|
220
|
+
@statement.should match(/uint tags from query/)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should include the SQL statement" do
|
224
|
+
@statement.should match(/SELECT cricket_team_id, id FROM tags/)
|
225
|
+
end
|
226
|
+
end
|
212
227
|
end
|
@@ -51,4 +51,94 @@ describe ThinkingSphinx::Index do
|
|
51
51
|
@index.infix_fields.should_not include(@field_b)
|
52
52
|
end
|
53
53
|
end
|
54
|
+
|
55
|
+
describe "multi-value attribute as ranged-query with has-many association" do
|
56
|
+
before :each do
|
57
|
+
@index = ThinkingSphinx::Index.new(Person) do
|
58
|
+
has tags(:id), :as => :tag_ids, :source => :ranged_query
|
59
|
+
end
|
60
|
+
@index.link!
|
61
|
+
|
62
|
+
@sql = @index.to_riddle_for_core(0, 0).sql_query
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not include attribute in select-clause sql_query" do
|
66
|
+
@sql.should_not match(/tag_ids/)
|
67
|
+
@sql.should_not match(/GROUP_CONCAT\(`tags`.`id`/)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should not join with association table" do
|
71
|
+
@sql.should_not match(/LEFT OUTER JOIN `tags`/)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should include sql_attr_multi as ranged-query" do
|
75
|
+
attribute = @index.send(:attributes).first
|
76
|
+
attribute.send(:type_to_config).to_s.should == "sql_attr_multi"
|
77
|
+
|
78
|
+
declaration, query, range_query = attribute.send(:config_value).split('; ')
|
79
|
+
declaration.should == "uint tag_ids from ranged-query"
|
80
|
+
query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end"
|
81
|
+
range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "multi-value attribute as ranged-query with has-many-through association" do
|
86
|
+
before :each do
|
87
|
+
@index = ThinkingSphinx::Index.new(Person) do
|
88
|
+
has football_teams(:id), :as => :football_teams_ids, :source => :ranged_query
|
89
|
+
end
|
90
|
+
@index.link!
|
91
|
+
|
92
|
+
@sql = @index.to_riddle_for_core(0, 0).sql_query
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should not include attribute in select-clause sql_query" do
|
96
|
+
@sql.should_not match(/football_teams_ids/)
|
97
|
+
@sql.should_not match(/GROUP_CONCAT\(`tags`.`football_team_id`/)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should not join with association table" do
|
101
|
+
@sql.should_not match(/LEFT OUTER JOIN `tags`/)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should include sql_attr_multi as ranged-query" do
|
105
|
+
attribute = @index.send(:attributes).first
|
106
|
+
attribute.send(:type_to_config).to_s.should == "sql_attr_multi"
|
107
|
+
|
108
|
+
declaration, query, range_query = attribute.send(:config_value).split('; ')
|
109
|
+
declaration.should == "uint football_teams_ids from ranged-query"
|
110
|
+
query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`football_team_id` AS `football_teams_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end"
|
111
|
+
range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "multi-value attribute as ranged-query with has-many-through association and foreign_key" do
|
116
|
+
before :each do
|
117
|
+
@index = ThinkingSphinx::Index.new(Person) do
|
118
|
+
has friends(:id), :as => :friend_ids, :source => :ranged_query
|
119
|
+
end
|
120
|
+
@index.link!
|
121
|
+
|
122
|
+
@sql = @index.to_riddle_for_core(0, 0).sql_query
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should not include attribute in select-clause sql_query" do
|
126
|
+
@sql.should_not match(/friend_ids/)
|
127
|
+
@sql.should_not match(/GROUP_CONCAT\(`friendships`.`friend_id`/)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should not join with association table" do
|
131
|
+
@sql.should_not match(/LEFT OUTER JOIN `friendships`/)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should include sql_attr_multi as ranged-query" do
|
135
|
+
attribute = @index.send(:attributes).first
|
136
|
+
attribute.send(:type_to_config).to_s.should == "sql_attr_multi"
|
137
|
+
|
138
|
+
declaration, query, range_query = attribute.send(:config_value).split('; ')
|
139
|
+
declaration.should == "uint friend_ids from ranged-query"
|
140
|
+
query.should == "SELECT `friendships`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `friendships`.`friend_id` AS `friend_ids` FROM `friendships` WHERE `friendships`.`person_id` >= $start AND `friendships`.`person_id` <= $end"
|
141
|
+
range_query.should == "SELECT MIN(`friendships`.`person_id`), MAX(`friendships`.`person_id`) FROM `friendships`"
|
142
|
+
end
|
143
|
+
end
|
54
144
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: freelancing-god-thinking-sphinx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pat Allan
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-03-11 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|