thinking-sphinx 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/README.textile +4 -0
  2. data/VERSION +1 -1
  3. data/features/facets.feature +18 -20
  4. data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
  5. data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
  6. data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
  7. data/features/thinking_sphinx/models/developer.rb +5 -1
  8. data/lib/thinking_sphinx.rb +10 -0
  9. data/lib/thinking_sphinx/active_record.rb +2 -0
  10. data/lib/thinking_sphinx/active_record/attribute_updates.rb +2 -0
  11. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +9 -0
  12. data/lib/thinking_sphinx/attribute.rb +1 -1
  13. data/lib/thinking_sphinx/auto_version.rb +1 -1
  14. data/lib/thinking_sphinx/configuration.rb +36 -9
  15. data/lib/thinking_sphinx/deltas/default_delta.rb +4 -4
  16. data/lib/thinking_sphinx/deploy/capistrano.rb +3 -2
  17. data/lib/thinking_sphinx/facet.rb +6 -4
  18. data/lib/thinking_sphinx/facet_search.rb +2 -0
  19. data/lib/thinking_sphinx/field.rb +2 -0
  20. data/lib/thinking_sphinx/property.rb +24 -7
  21. data/lib/thinking_sphinx/search.rb +48 -10
  22. data/lib/thinking_sphinx/source.rb +5 -1
  23. data/lib/thinking_sphinx/source/sql.rb +20 -4
  24. data/lib/thinking_sphinx/tasks.rb +1 -1
  25. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +11 -0
  26. data/spec/thinking_sphinx/attribute_spec.rb +9 -0
  27. data/spec/thinking_sphinx/auto_version_spec.rb +8 -0
  28. data/spec/thinking_sphinx/configuration_spec.rb +17 -0
  29. data/spec/thinking_sphinx/facet_spec.rb +14 -0
  30. data/spec/thinking_sphinx/field_spec.rb +11 -0
  31. data/spec/thinking_sphinx/search_spec.rb +53 -0
  32. data/spec/thinking_sphinx/source_spec.rb +10 -0
  33. metadata +7 -7
@@ -193,3 +193,7 @@ Since I first released this library, there's been quite a few people who have su
193
193
  * Ivan Ukhov
194
194
  * Stephen Celis
195
195
  * Paco Guzmán
196
+ * Marcin Stecki
197
+ * Robert Glaser
198
+ * Brenton Fletcher
199
+ * Paul Schyska
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.1
1
+ 2.0.2
@@ -1,8 +1,10 @@
1
1
  Feature: Search and browse models by their defined facets
2
2
 
3
- Scenario: Requesting facets
3
+ Background:
4
4
  Given Sphinx is running
5
- And I am searching on developers
5
+
6
+ Scenario: Requesting facets
7
+ Given I am searching on developers
6
8
  When I am requesting facet results
7
9
  Then I should have valid facet results
8
10
  And I should have 6 facets
@@ -14,8 +16,7 @@ Feature: Search and browse models by their defined facets
14
16
  And I should have the facet Tags
15
17
 
16
18
  Scenario: Requesting specific facets
17
- Given Sphinx is running
18
- And I am searching on developers
19
+ Given I am searching on developers
19
20
  When I am requesting facet results
20
21
  And I am requesting just the facet State
21
22
  Then I should have valid facet results
@@ -28,29 +29,25 @@ Feature: Search and browse models by their defined facets
28
29
  And I should have the facet Age
29
30
 
30
31
  Scenario: Requesting float facets
31
- Given Sphinx is running
32
- And I am searching on alphas
32
+ Given I am searching on alphas
33
33
  When I am requesting facet results
34
34
  Then I should have 1 facet
35
35
  And the Cost facet should have a 5.55 key
36
36
 
37
37
  Scenario: Requesting facet results
38
- Given Sphinx is running
39
- And I am searching on developers
38
+ Given I am searching on developers
40
39
  When I am requesting facet results
41
40
  And I drill down where Country is Australia
42
41
  Then I should get 11 results
43
42
 
44
43
  Scenario: Requesting facet results by multiple facets
45
- Given Sphinx is running
46
- And I am searching on developers
44
+ Given I am searching on developers
47
45
  When I am requesting facet results
48
46
  And I drill down where Country is Australia and Age is 30
49
47
  Then I should get 4 results
50
48
 
51
49
  Scenario: Requesting facets with classes included
52
- Given Sphinx is running
53
- And I am searching on developers
50
+ Given I am searching on developers
54
51
  When I am requesting facet results
55
52
  And I want classes included
56
53
  Then I should have valid facet results
@@ -58,8 +55,7 @@ Feature: Search and browse models by their defined facets
58
55
  And I should have the facet Class
59
56
 
60
57
  Scenario: Requesting MVA facets
61
- Given Sphinx is running
62
- And I am searching on developers
58
+ Given I am searching on developers
63
59
  When I am requesting facet results
64
60
  And I drill down where tag_ids includes the id of tag Australia
65
61
  Then I should get 11 results
@@ -68,23 +64,25 @@ Feature: Search and browse models by their defined facets
68
64
  Then I should get 5 results
69
65
 
70
66
  Scenario: Requesting MVA string facets
71
- Given Sphinx is running
72
- And I am searching on developers
67
+ Given I am searching on developers
73
68
  When I am requesting facet results
74
69
  Then the Tags facet should have an "Australia" key
75
70
  Then the Tags facet should have an "Melbourne" key
76
71
  Then the Tags facet should have an "Victoria" key
77
72
 
78
73
  Scenario: Requesting MVA facets from source queries
79
- Given Sphinx is running
80
- And I am searching on posts
74
+ Given I am searching on posts
81
75
  When I am requesting facet results
82
76
  Then the Comment Ids facet should have 9 keys
83
77
 
84
78
  Scenario: Requesting facets from a subclass
85
- Given Sphinx is running
86
- And I am searching on animals
79
+ Given I am searching on animals
87
80
  When I am requesting facet results
88
81
  And I want classes included
89
82
  Then I should have the facet Class
90
83
 
84
+ Scenario: Requesting facets with explicit value sources
85
+ Given I am searching on developers
86
+ When I am requesting facet results
87
+ Then the City facet should have a "Melbourne" key
88
+
@@ -1,3 +1,3 @@
1
1
  %w( rogue nat molly jasper moggy ).each do |name|
2
- Cat.create :name => name
2
+ Cat.new(:name => name).save(false)
3
3
  end
@@ -1,3 +1,3 @@
1
1
  %w( rover lassie gaspode ).each do |name|
2
- Dog.create :name => name
2
+ Dog.new(:name => name).save(false)
3
3
  end
@@ -1,3 +1,3 @@
1
1
  %w( fantastic ).each do |name|
2
- Fox.create :name => name
2
+ Fox.new(:name => name).save(false)
3
3
  end
@@ -9,8 +9,12 @@ class Developer < ActiveRecord::Base
9
9
  indexes country, :facet => true
10
10
  indexes state, :facet => true
11
11
  indexes tags.text, :as => :tags, :facet => true
12
+
12
13
  has age, :facet => true
13
14
  has tags(:id), :as => :tag_ids, :facet => true
14
- facet city
15
+
16
+ facet "LOWER(city)", :as => :city, :type => :string, :value => :city
17
+
18
+ group_by 'city'
15
19
  end
16
20
  end
@@ -47,6 +47,16 @@ module ThinkingSphinx
47
47
  end
48
48
  end
49
49
 
50
+ # A SphinxError occurs when Sphinx responds with an error due to problematic
51
+ # queries or indexes.
52
+ class SphinxError < RuntimeError
53
+ attr_accessor :results
54
+ def initialize(message = nil, results = nil)
55
+ super(message)
56
+ self.results = results
57
+ end
58
+ end
59
+
50
60
  # The current version of Thinking Sphinx.
51
61
  #
52
62
  # @return [String] The version number as a string
@@ -245,6 +245,8 @@ module ThinkingSphinx
245
245
  ThinkingSphinx::Configuration.instance.client.update(
246
246
  index, ['sphinx_deleted'], {document_id => [1]}
247
247
  )
248
+ rescue Riddle::ConnectionError, ThinkingSphinx::SphinxError
249
+ # Not the end of the world if Sphinx isn't running.
248
250
  end
249
251
 
250
252
  def sphinx_offset
@@ -44,6 +44,8 @@ module ThinkingSphinx
44
44
  config.client.update index_name, attribute_names, {
45
45
  sphinx_document_id => attribute_values
46
46
  } if self.class.search_for_id(sphinx_document_id, index_name)
47
+ rescue Riddle::ConnectionError, ThinkingSphinx::SphinxError
48
+ # Not the end of the world if Sphinx isn't running.
47
49
  end
48
50
  end
49
51
  end
@@ -16,6 +16,8 @@ module ThinkingSphinx
16
16
  ThinkingSphinx::MysqlAdapter.new model
17
17
  when :postgresql
18
18
  ThinkingSphinx::PostgreSQLAdapter.new model
19
+ when Class
20
+ adapter.new model
19
21
  else
20
22
  raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{adapter}"
21
23
  end
@@ -69,6 +71,13 @@ module ThinkingSphinx
69
71
  "LOWER(#{clause})"
70
72
  end
71
73
 
74
+ def case(expression, pairs, default)
75
+ "CASE #{expression} " +
76
+ pairs.keys.inject('') { |string, key|
77
+ string + "WHEN '#{key}' THEN #{pairs[key]} "
78
+ } + "ELSE #{default} END"
79
+ end
80
+
72
81
  protected
73
82
 
74
83
  def connection
@@ -89,7 +89,7 @@ module ThinkingSphinx
89
89
  # datetimes to timestamps, as needed.
90
90
  #
91
91
  def to_select_sql
92
- return nil unless include_as_association?
92
+ return nil unless include_as_association? && available?
93
93
 
94
94
  separator = all_ints? || all_datetimes? || @crc ? ',' : ' '
95
95
 
@@ -5,7 +5,7 @@ module ThinkingSphinx
5
5
  case version
6
6
  when '0.9.8', '0.9.9'
7
7
  require "riddle/#{version}"
8
- when '1.10-beta'
8
+ when '1.10-beta', '1.10-id64-beta'
9
9
  require 'riddle/1.10'
10
10
  else
11
11
  STDERR.puts %Q{
@@ -139,22 +139,28 @@ module ThinkingSphinx
139
139
  def environment
140
140
  self.class.environment
141
141
  end
142
-
143
- # Generate the config file for Sphinx by using all the settings defined and
144
- # looping through all the models with indexes to build the relevant
145
- # indexer and searchd configuration, and sources and indexes details.
146
- #
147
- def build(file_path=nil)
148
- file_path ||= "#{self.config_file}"
149
-
142
+
143
+ def generate
150
144
  @configuration.indexes.clear
151
145
 
152
146
  ThinkingSphinx.context.indexed_models.each do |model|
153
147
  model = model.constantize
154
148
  model.define_indexes
155
149
  @configuration.indexes.concat model.to_riddle
150
+
151
+ enforce_common_attribute_types
156
152
  end
157
-
153
+ end
154
+
155
+ # Generate the config file for Sphinx by using all the settings defined and
156
+ # looping through all the models with indexes to build the relevant
157
+ # indexer and searchd configuration, and sources and indexes details.
158
+ #
159
+ def build(file_path=nil)
160
+ file_path ||= "#{self.config_file}"
161
+
162
+ generate
163
+
158
164
  open(file_path, "w") do |file|
159
165
  file.write @configuration.render
160
166
  end
@@ -292,5 +298,26 @@ module ThinkingSphinx
292
298
  send("#{key}=", value) if self.respond_to?("#{key}")
293
299
  end
294
300
  end
301
+
302
+ def enforce_common_attribute_types
303
+ sql_indexes = configuration.indexes.reject { |index|
304
+ index.is_a? Riddle::Configuration::DistributedIndex
305
+ }
306
+
307
+ return unless sql_indexes.any? { |index|
308
+ index.sources.any? { |source|
309
+ source.sql_attr_bigint.include? :sphinx_internal_id
310
+ }
311
+ }
312
+
313
+ sql_indexes.each { |index|
314
+ index.sources.each { |source|
315
+ next if source.sql_attr_bigint.include? :sphinx_internal_id
316
+
317
+ source.sql_attr_bigint << :sphinx_internal_id
318
+ source.sql_attr_uint.delete :sphinx_internal_id
319
+ }
320
+ }
321
+ end
295
322
  end
296
323
  end
@@ -20,13 +20,13 @@ module ThinkingSphinx
20
20
  end
21
21
 
22
22
  def toggle(instance)
23
- instance.delta = true
23
+ instance.send "#{@column}=", true
24
24
  end
25
-
25
+
26
26
  def toggled(instance)
27
- instance.delta
27
+ instance.send "#{@column}"
28
28
  end
29
-
29
+
30
30
  def reset_query(model)
31
31
  "UPDATE #{model.quoted_table_name} SET " +
32
32
  "#{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(false)} " +
@@ -84,9 +84,10 @@ DESC
84
84
  start
85
85
  end
86
86
 
87
- desc "Add the shared folder for sphinx files for the production environment"
87
+ desc "Add the shared folder for sphinx files"
88
88
  task :shared_sphinx_folder, :roles => :web do
89
- run "mkdir -p #{shared_path}/db/sphinx/production"
89
+ rails_env = fetch(:rails_env, "production")
90
+ run "mkdir -p #{shared_path}/sphinx/#{rails_env}"
90
91
  end
91
92
 
92
93
  def rake(*tasks)
@@ -1,9 +1,10 @@
1
1
  module ThinkingSphinx
2
2
  class Facet
3
- attr_reader :property
3
+ attr_reader :property, :value_source
4
4
 
5
- def initialize(property)
6
- @property = property
5
+ def initialize(property, value_source = nil)
6
+ @property = property
7
+ @value_source = value_source
7
8
 
8
9
  if property.columns.length != 1
9
10
  raise "Can't translate Facets on multiple-column field or attribute"
@@ -104,7 +105,8 @@ module ThinkingSphinx
104
105
  item.to_crc32 == attribute_value
105
106
  }
106
107
  else
107
- objects.first.send(column.__name)
108
+ method = value_source || column.__name
109
+ objects.first.send(method)
108
110
  end
109
111
  end
110
112
 
@@ -44,6 +44,8 @@ module ThinkingSphinx
44
44
  end
45
45
 
46
46
  def populate
47
+ return if facet_names.empty?
48
+
47
49
  ThinkingSphinx::Search.bundle_searches(facet_names) { |sphinx, name|
48
50
  sphinx.search *(args + [facet_search_options(name)])
49
51
  }.each_with_index { |search, index|
@@ -69,6 +69,8 @@ module ThinkingSphinx
69
69
  # multiple data values (has_many or has_and_belongs_to_many associations).
70
70
  #
71
71
  def to_select_sql
72
+ return nil unless available?
73
+
72
74
  clause = columns_with_prefixes.join(', ')
73
75
 
74
76
  clause = adapter.concatenate(clause) if concat_ws?
@@ -10,10 +10,11 @@ module ThinkingSphinx
10
10
 
11
11
  raise "Cannot define a field or attribute in #{source.model.name} with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) }
12
12
 
13
- @alias = options[:as]
14
- @faceted = options[:facet]
15
- @admin = options[:admin]
16
- @sortable = options[:sortable] || false
13
+ @alias = options[:as]
14
+ @faceted = options[:facet]
15
+ @admin = options[:admin]
16
+ @sortable = options[:sortable] || false
17
+ @value_source = options[:value]
17
18
 
18
19
  @alias = @alias.to_sym unless @alias.blank?
19
20
 
@@ -40,7 +41,7 @@ module ThinkingSphinx
40
41
  def to_facet
41
42
  return nil unless @faceted
42
43
 
43
- ThinkingSphinx::Facet.new(self)
44
+ ThinkingSphinx::Facet.new(self, @value_source)
44
45
  end
45
46
 
46
47
  # Get the part of the GROUP BY clause related to this attribute - if one is
@@ -76,6 +77,10 @@ module ThinkingSphinx
76
77
  !admin
77
78
  end
78
79
 
80
+ def available?
81
+ columns.any? { |column| column_available?(column) }
82
+ end
83
+
79
84
  private
80
85
 
81
86
  # Could there be more than one value related to the parent record? If so,
@@ -127,9 +132,11 @@ module ThinkingSphinx
127
132
  # figure out how to correctly reference a column in SQL.
128
133
  #
129
134
  def column_with_prefix(column)
135
+ return nil unless column_available?(column)
136
+
130
137
  if column.is_string?
131
138
  column.__name
132
- elsif associations[column].empty?
139
+ elsif column.__stack.empty?
133
140
  "#{@model.quoted_table_name}.#{quote_column(column.__name)}"
134
141
  else
135
142
  associations[column].collect { |assoc|
@@ -143,7 +150,17 @@ module ThinkingSphinx
143
150
  def columns_with_prefixes
144
151
  @columns.collect { |column|
145
152
  column_with_prefix column
146
- }.flatten
153
+ }.flatten.compact
154
+ end
155
+
156
+ def column_available?(column)
157
+ if column.is_string?
158
+ true
159
+ elsif column.__stack.empty?
160
+ @model.column_names.include?(column.__name.to_s)
161
+ else
162
+ associations[column].any? { |assoc| assoc.has_column?(column.__name) }
163
+ end
147
164
  end
148
165
 
149
166
  # Gets a stack of associations for a specific path.
@@ -120,6 +120,40 @@ module ThinkingSphinx
120
120
  !!@populated
121
121
  end
122
122
 
123
+ # Indication of whether the request resulted in an error from Sphinx.
124
+ #
125
+ # @return [Boolean] true if Sphinx reports query error
126
+ #
127
+ def error?
128
+ !!error
129
+ end
130
+
131
+ # The Sphinx-reported error, if any.
132
+ #
133
+ # @return [String, nil]
134
+ #
135
+ def error
136
+ populate
137
+ @results[:error]
138
+ end
139
+
140
+ # Indication of whether the request resulted in a warning from Sphinx.
141
+ #
142
+ # @return [Boolean] true if Sphinx reports query warning
143
+ #
144
+ def warning?
145
+ !!warning
146
+ end
147
+
148
+ # The Sphinx-reported warning, if any.
149
+ #
150
+ # @return [String, nil]
151
+ #
152
+ def warning
153
+ populate
154
+ @results[:warning]
155
+ end
156
+
123
157
  # The query result hash from Riddle.
124
158
  #
125
159
  # @return [Hash] Raw Sphinx results
@@ -358,6 +392,13 @@ module ThinkingSphinx
358
392
  end
359
393
  total = @results[:total_found].to_i
360
394
  log "Found #{total} result#{'s' unless total == 1}"
395
+
396
+ log "Sphinx Daemon returned warning: #{warning}" if warning?
397
+
398
+ if error?
399
+ log "Sphinx Daemon returned error: #{error}"
400
+ raise SphinxError.new(error, @results) unless options[:ignore_errors]
401
+ end
361
402
  rescue Errno::ECONNREFUSED => err
362
403
  raise ThinkingSphinx::ConnectionError,
363
404
  'Connection to Sphinx Daemon (searchd) failed.'
@@ -432,9 +473,11 @@ module ThinkingSphinx
432
473
  end
433
474
 
434
475
  def prepare(client)
435
- index_options = one_class ?
436
- one_class.sphinx_indexes.first.local_options : {}
437
-
476
+ index_options = {}
477
+ if one_class && one_class.sphinx_indexes && one_class.sphinx_indexes.first
478
+ index_options = one_class.sphinx_indexes.first.local_options
479
+ end
480
+
438
481
  [
439
482
  :max_matches, :group_by, :group_function, :group_clause,
440
483
  :group_distinct, :id_range, :cut_off, :retry_count, :retry_delay,
@@ -506,12 +549,7 @@ module ThinkingSphinx
506
549
  def conditions_as_query
507
550
  return '' if @options[:conditions].blank?
508
551
 
509
- # Soon to be deprecated.
510
- keys = @options[:conditions].keys.reject { |key|
511
- attributes.include?(key.to_sym)
512
- }
513
-
514
- ' ' + keys.collect { |key|
552
+ ' ' + @options[:conditions].keys.collect { |key|
515
553
  "@#{key} #{options[:conditions][key]}"
516
554
  }.join(' ')
517
555
  end
@@ -566,7 +604,7 @@ module ThinkingSphinx
566
604
  def sort_by
567
605
  case @sort_by = (options[:sort_by] || options[:order])
568
606
  when String
569
- sorted_fields_to_attributes(@sort_by)
607
+ sorted_fields_to_attributes(@sort_by.clone)
570
608
  when Symbol
571
609
  field_names.include?(@sort_by) ?
572
610
  @sort_by.to_s.concat('_sort') : @sort_by.to_s
@@ -82,6 +82,10 @@ module ThinkingSphinx
82
82
  @adapter ||= @model.sphinx_database_adapter
83
83
  end
84
84
 
85
+ def available_attributes
86
+ attributes.select { |attrib| attrib.available? }
87
+ end
88
+
85
89
  def set_source_database_settings(source)
86
90
  config = @database_configuration
87
91
 
@@ -94,7 +98,7 @@ module ThinkingSphinx
94
98
  end
95
99
 
96
100
  def set_source_attributes(source, offset, delta = false)
97
- attributes.each do |attrib|
101
+ available_attributes.each do |attrib|
98
102
  source.send(attrib.type_to_config) << attrib.config_value(offset, delta)
99
103
  end
100
104
  end
@@ -119,14 +119,30 @@ module ThinkingSphinx
119
119
  if @model.table_exists? &&
120
120
  @model.column_names.include?(@model.inheritance_column)
121
121
 
122
- adapter.cast_to_unsigned(adapter.convert_nulls(
123
- adapter.crc(adapter.quote_with_table(@model.inheritance_column), true),
124
- @model.to_crc32
125
- ))
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_to_crcs, @model.to_crc32)
126
128
  else
127
129
  @model.to_crc32.to_s
128
130
  end
129
131
  end
132
+
133
+ def type_values
134
+ @model.connection.select_values <<-SQL
135
+ SELECT DISTINCT #{@model.inheritance_column}
136
+ FROM #{@model.table_name}
137
+ SQL
138
+ end
139
+
140
+ def types_to_crcs
141
+ type_values.compact.inject({}) { |hash, type|
142
+ hash[type] = type.to_crc32
143
+ hash
144
+ }
145
+ end
130
146
  end
131
147
  end
132
148
  end
@@ -4,7 +4,7 @@ require 'timeout'
4
4
  namespace :thinking_sphinx do
5
5
  task :app_env do
6
6
  if defined?(Rails)
7
- Rake::Task[:environment].invoke
7
+ Rails.application.require_environment!
8
8
  Rails.configuration.cache_classes = false
9
9
  end
10
10
 
@@ -1,5 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
+ class CustomAdapter < ThinkingSphinx::AbstractAdapter
4
+ #
5
+ end
6
+
3
7
  describe ThinkingSphinx::AbstractAdapter do
4
8
  describe '.detect' do
5
9
  let(:model) { stub('model') }
@@ -18,6 +22,13 @@ describe ThinkingSphinx::AbstractAdapter do
18
22
  adapter.should be_a(ThinkingSphinx::PostgreSQLAdapter)
19
23
  end
20
24
 
25
+ it "instantiates the provided class if one is provided" do
26
+ ThinkingSphinx::AbstractAdapter.stub(:adapter_for_model => CustomAdapter)
27
+ CustomAdapter.should_receive(:new).and_return(stub('adapter'))
28
+
29
+ ThinkingSphinx::AbstractAdapter.detect(model)
30
+ end
31
+
21
32
  it "raises an exception for other responses" do
22
33
  ThinkingSphinx::AbstractAdapter.stub(:adapter_for_model => :sqlite)
23
34
 
@@ -68,6 +68,15 @@ describe ThinkingSphinx::Attribute do
68
68
 
69
69
  attribute.to_select_sql.should == "CONCAT_WS(' ', IFNULL(`cricket_teams`.`name`, ''), IFNULL(`football_teams`.`name`, ''), IFNULL(`football_teams`.`league`, '')) AS `team`"
70
70
  end
71
+
72
+ it "should return nil if polymorphic association data does not exist" do
73
+ attribute = ThinkingSphinx::Attribute.new(@source,
74
+ [ThinkingSphinx::Index::FauxColumn.new(:source, :id)],
75
+ :as => :source_id, :type => :integer
76
+ )
77
+
78
+ attribute.to_select_sql.should be_nil
79
+ end
71
80
  end
72
81
 
73
82
  describe '#is_many?' do
@@ -30,6 +30,14 @@ describe ThinkingSphinx::AutoVersion do
30
30
  ThinkingSphinx::AutoVersion.detect
31
31
  end
32
32
 
33
+ it "should require 1.10-beta if that is the detected version" do
34
+ ThinkingSphinx::AutoVersion.should_receive(:require).
35
+ with('riddle/1.10')
36
+
37
+ @config.stub!(:version => '1.10-id64-beta')
38
+ ThinkingSphinx::AutoVersion.detect
39
+ end
40
+
33
41
  it "should output a warning if the detected version is something else" do
34
42
  STDERR.should_receive(:puts)
35
43
 
@@ -224,6 +224,23 @@ describe ThinkingSphinx::Configuration do
224
224
  file.should_not match(/index alpha_core\s+\{\s+[^\}]*prefix_fields\s+=[^\}]*\}/m)
225
225
  end
226
226
 
227
+ describe '#generate' do
228
+ let(:config) { ThinkingSphinx::Configuration.instance }
229
+
230
+ it "should set all sphinx_internal_id attributes to bigints if one is" do
231
+ config.reset
232
+ config.generate
233
+
234
+ config.configuration.indexes.each do |index|
235
+ next if index.is_a? Riddle::Configuration::DistributedIndex
236
+
237
+ index.sources.each do |source|
238
+ source.sql_attr_bigint.should include(:sphinx_internal_id)
239
+ end
240
+ end
241
+ end
242
+ end
243
+
227
244
  describe '#client' do
228
245
  before :each do
229
246
  @config = ThinkingSphinx::Configuration.instance
@@ -329,5 +329,19 @@ describe ThinkingSphinx::Facet do
329
329
  @facet.value(alpha, {'cost' => 1093140480}).should == 10.5
330
330
  end
331
331
  end
332
+
333
+ context 'manual value source' do
334
+ let(:index) { ThinkingSphinx::Index.new(Alpha) }
335
+ let(:source) { ThinkingSphinx::Source.new(index) }
336
+ let(:column) { ThinkingSphinx::Index::FauxColumn.new('LOWER(name)') }
337
+ let(:field) { ThinkingSphinx::Field.new(source, column) }
338
+ let(:facet) { ThinkingSphinx::Facet.new(field, :name) }
339
+
340
+ it "should use the given value source to figure out the value" do
341
+ alpha = Alpha.new(:name => 'Foo')
342
+
343
+ facet.value(alpha, {'foo_facet' => 'foo'.to_crc32}).should == 'Foo'
344
+ end
345
+ end
332
346
  end
333
347
  end
@@ -44,6 +44,17 @@ describe ThinkingSphinx::Field do
44
44
  @field.unique_name.should == "col_name"
45
45
  end
46
46
  end
47
+
48
+ describe '#to_select_sql' do
49
+ it "should return nil if polymorphic association data does not exist" do
50
+ field = ThinkingSphinx::Field.new(@source,
51
+ [ThinkingSphinx::Index::FauxColumn.new(:source, :name)],
52
+ :as => :source_name
53
+ )
54
+
55
+ field.to_select_sql.should be_nil
56
+ end
57
+ end
47
58
 
48
59
  describe "prefixes method" do
49
60
  it "should default to false" do
@@ -52,6 +52,38 @@ describe ThinkingSphinx::Search do
52
52
  end
53
53
  end
54
54
 
55
+ describe '#error?' do
56
+ before :each do
57
+ @search = ThinkingSphinx::Search.new
58
+ end
59
+
60
+ it "should be false if client requests have not resulted in an error" do
61
+ @search.should_receive(:error).and_return(nil)
62
+ @search.error?.should_not be_true
63
+ end
64
+
65
+ it "should be true when client requests result in an error" do
66
+ @search.should_receive(:error).and_return("error message")
67
+ @search.error?.should be_true
68
+ end
69
+ end
70
+
71
+ describe '#warning?' do
72
+ before :each do
73
+ @search = ThinkingSphinx::Search.new
74
+ end
75
+
76
+ it "should be false if client requests have not resulted in a warning" do
77
+ @search.should_receive(:warning).and_return(nil)
78
+ @search.warning?.should_not be_true
79
+ end
80
+
81
+ it "should be true when client requests result in an error" do
82
+ @search.should_receive(:warning).and_return("warning message")
83
+ @search.warning?.should be_true
84
+ end
85
+ end
86
+
55
87
  describe '#results' do
56
88
  it "should populate search results before returning" do
57
89
  @search = ThinkingSphinx::Search.new
@@ -918,6 +950,27 @@ describe ThinkingSphinx::Search do
918
950
  end
919
951
  end
920
952
  end
953
+
954
+ context 'Sphinx errors' do
955
+ describe '#error?' do
956
+ before :each do
957
+ @client.stub! :query => {
958
+ :error => @warning = "Not good"
959
+ }
960
+ # @search.should_receive(:error).and_return(nil)
961
+ end
962
+ it "should raise an error" do
963
+ lambda{
964
+ ThinkingSphinx::Search.new.first
965
+ }.should raise_error(ThinkingSphinx::SphinxError)
966
+ end
967
+ it "should not raise an error when ignore_errors is true" do
968
+ lambda{
969
+ ThinkingSphinx::Search.new(:ignore_errors => true).first
970
+ }.should_not raise_error(ThinkingSphinx::SphinxError)
971
+ end
972
+ end
973
+ end
921
974
  end
922
975
 
923
976
  describe '#current_page' do
@@ -48,6 +48,10 @@ describe ThinkingSphinx::Source do
48
48
  @source, ThinkingSphinx::Index::FauxColumn.new(:contacts, :id),
49
49
  :as => :contact_ids, :source => :query
50
50
  )
51
+ ThinkingSphinx::Attribute.new(
52
+ @source, ThinkingSphinx::Index::FauxColumn.new(:source, :id),
53
+ :as => :source_id, :type => :integer
54
+ )
51
55
 
52
56
  ThinkingSphinx::Join.new(
53
57
  @source, ThinkingSphinx::Index::FauxColumn.new(:links)
@@ -103,6 +107,12 @@ describe ThinkingSphinx::Source do
103
107
  @riddle.sql_attr_timestamp.first.should == :birthday
104
108
  end
105
109
 
110
+ it "should not include an attribute definition for polymorphic references without data" do
111
+ @riddle.sql_attr_uint.select { |uint|
112
+ uint == :source_id
113
+ }.should be_empty
114
+ end
115
+
106
116
  it "should set Sphinx Source options" do
107
117
  @riddle.sql_range_step.should == 1000
108
118
  @riddle.sql_ranged_throttle.should == 100
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thinking-sphinx
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
8
  - 0
9
- - 1
10
- version: 2.0.1
9
+ - 2
10
+ version: 2.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Pat Allan
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-20 00:00:00 +11:00
18
+ date: 2011-01-13 00:00:00 +11:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -43,12 +43,12 @@ dependencies:
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- hash: 29
46
+ hash: 27
47
47
  segments:
48
48
  - 1
49
49
  - 2
50
- - 1
51
- version: 1.2.1
50
+ - 2
51
+ version: 1.2.2
52
52
  requirement: *id002
53
53
  - !ruby/object:Gem::Dependency
54
54
  type: :development