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 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
@@ -34,7 +34,7 @@ module ThinkingSphinx
34
34
  module Version #:nodoc:
35
35
  Major = 1
36
36
  Minor = 1
37
- Tiny = 5
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
- self.class.search_for_id(
212
- self.sphinx_document_id,
213
- "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_core"
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
 
@@ -13,7 +13,7 @@ module ThinkingSphinx
13
13
  end
14
14
 
15
15
  def group_concatenate(clause, separator = ' ')
16
- "GROUP_CONCAT(#{clause} SEPARATOR '#{separator}')"
16
+ "GROUP_CONCAT(DISTINCT #{clause} SEPARATOR '#{separator}')"
17
17
  end
18
18
 
19
19
  def cast_to_string(clause)
@@ -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 => :alias_name
19
- # - :type => :attribute_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 config_value
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
- "uint #{unique_name} from field"
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
- "#{@model.connection.quote_table_name(assoc.join.aliased_table_name)}" +
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
- result = 0xFFFFFFFF
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
- case options.delete(:delta)
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
- raise "Unknown delta type"
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.in_core_index?
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
@@ -27,7 +27,7 @@ module ThinkingSphinx
27
27
  return translate(object, attribute_value) if @reference.is_a?(Field)
28
28
 
29
29
  case @reference.type
30
- when :string, :multi
30
+ when :string
31
31
  translate(object, attribute_value)
32
32
  when :datetime
33
33
  Time.at(attribute_value)
@@ -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 = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
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(method)
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 two ways to do
98
- # this. The first is one that works in all scenarios - using the :with
99
- # option.
100
- #
101
- # ThinkingSphinx::Search.search :with => {:parent_id => 10}
102
- #
103
- # The second is only viable if you're searching with a specific model
104
- # (not multi-model searching). With a single model, Thinking Sphinx
105
- # can figure out what attributes and fields are available, so you can
106
- # put it all in the :conditions hash, and it will sort it out.
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: 3 # default to three retries
295
- when nil, false: 0 # no retries
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
- :in_core_index? => false,
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(:in_core_index? => true)
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.5
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-02-09 00:00:00 -08:00
12
+ date: 2009-03-11 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15