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.
- 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
|