thinking-sphinx 2.0.1 → 2.0.2
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/README.textile +4 -0
- data/VERSION +1 -1
- data/features/facets.feature +18 -20
- data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
- data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
- data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
- data/features/thinking_sphinx/models/developer.rb +5 -1
- data/lib/thinking_sphinx.rb +10 -0
- data/lib/thinking_sphinx/active_record.rb +2 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +2 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +9 -0
- data/lib/thinking_sphinx/attribute.rb +1 -1
- data/lib/thinking_sphinx/auto_version.rb +1 -1
- data/lib/thinking_sphinx/configuration.rb +36 -9
- data/lib/thinking_sphinx/deltas/default_delta.rb +4 -4
- data/lib/thinking_sphinx/deploy/capistrano.rb +3 -2
- data/lib/thinking_sphinx/facet.rb +6 -4
- data/lib/thinking_sphinx/facet_search.rb +2 -0
- data/lib/thinking_sphinx/field.rb +2 -0
- data/lib/thinking_sphinx/property.rb +24 -7
- data/lib/thinking_sphinx/search.rb +48 -10
- data/lib/thinking_sphinx/source.rb +5 -1
- data/lib/thinking_sphinx/source/sql.rb +20 -4
- data/lib/thinking_sphinx/tasks.rb +1 -1
- data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +11 -0
- data/spec/thinking_sphinx/attribute_spec.rb +9 -0
- data/spec/thinking_sphinx/auto_version_spec.rb +8 -0
- data/spec/thinking_sphinx/configuration_spec.rb +17 -0
- data/spec/thinking_sphinx/facet_spec.rb +14 -0
- data/spec/thinking_sphinx/field_spec.rb +11 -0
- data/spec/thinking_sphinx/search_spec.rb +53 -0
- data/spec/thinking_sphinx/source_spec.rb +10 -0
- metadata +7 -7
data/README.textile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.0.
|
1
|
+
2.0.2
|
data/features/facets.feature
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
Feature: Search and browse models by their defined facets
|
2
2
|
|
3
|
-
|
3
|
+
Background:
|
4
4
|
Given Sphinx is running
|
5
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
+
|
@@ -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
|
-
|
15
|
+
|
16
|
+
facet "LOWER(city)", :as => :city, :type => :string, :value => :city
|
17
|
+
|
18
|
+
group_by 'city'
|
15
19
|
end
|
16
20
|
end
|
data/lib/thinking_sphinx.rb
CHANGED
@@ -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
|
|
@@ -139,22 +139,28 @@ module ThinkingSphinx
|
|
139
139
|
def environment
|
140
140
|
self.class.environment
|
141
141
|
end
|
142
|
-
|
143
|
-
|
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.
|
23
|
+
instance.send "#{@column}=", true
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def toggled(instance)
|
27
|
-
instance.
|
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
|
87
|
+
desc "Add the shared folder for sphinx files"
|
88
88
|
task :shared_sphinx_folder, :roles => :web do
|
89
|
-
|
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
|
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
|
-
|
108
|
+
method = value_source || column.__name
|
109
|
+
objects.first.send(method)
|
108
110
|
end
|
109
111
|
end
|
110
112
|
|
@@ -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
|
14
|
-
@faceted
|
15
|
-
@admin
|
16
|
-
@sortable
|
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
|
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 =
|
436
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
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
|
@@ -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:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 2.0.
|
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:
|
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:
|
46
|
+
hash: 27
|
47
47
|
segments:
|
48
48
|
- 1
|
49
49
|
- 2
|
50
|
-
-
|
51
|
-
version: 1.2.
|
50
|
+
- 2
|
51
|
+
version: 1.2.2
|
52
52
|
requirement: *id002
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
type: :development
|