DrMark-thinking-sphinx 0.9.8 → 0.9.9
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 +9 -1
- data/lib/thinking_sphinx/active_record/delta.rb +11 -1
- data/lib/thinking_sphinx/active_record/search.rb +8 -1
- data/lib/thinking_sphinx/active_record.rb +6 -0
- data/lib/thinking_sphinx/association.rb +4 -0
- data/lib/thinking_sphinx/attribute.rb +4 -2
- data/lib/thinking_sphinx/collection.rb +23 -0
- data/lib/thinking_sphinx/configuration.rb +41 -30
- data/lib/thinking_sphinx/field.rb +11 -3
- data/lib/thinking_sphinx/index/builder.rb +2 -19
- data/lib/thinking_sphinx/index/faux_column.rb +13 -0
- data/lib/thinking_sphinx/index.rb +65 -28
- data/lib/thinking_sphinx/search.rb +38 -29
- data/lib/thinking_sphinx.rb +21 -2
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +27 -6
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +26 -0
- data/spec/unit/thinking_sphinx/active_record_spec.rb +14 -1
- data/spec/unit/thinking_sphinx/attribute_spec.rb +6 -2
- data/spec/unit/thinking_sphinx/configuration_spec.rb +45 -27
- data/spec/unit/thinking_sphinx/field_spec.rb +11 -2
- data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +27 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +48 -23
- data/spec/unit/thinking_sphinx/search_spec.rb +80 -10
- data/spec/unit/thinking_sphinx_spec.rb +21 -0
- data/tasks/thinking_sphinx_tasks.rb +1 -1
- metadata +16 -2
data/README
CHANGED
@@ -54,4 +54,12 @@ Since I first released this library, there's been quite a few people who have su
|
|
54
54
|
- Andrew Bennett
|
55
55
|
- Jordan Fowler
|
56
56
|
- Seth Walker
|
57
|
-
- Joe Noon
|
57
|
+
- Joe Noon
|
58
|
+
- Wolfgang Postler
|
59
|
+
- Rick Olson
|
60
|
+
- Killian Murphy
|
61
|
+
- Morten Primdahl
|
62
|
+
- Ryan Bates
|
63
|
+
- David Eisinger
|
64
|
+
- Shay Arnett
|
65
|
+
- Minh Tran
|
@@ -73,7 +73,17 @@ module ThinkingSphinx
|
|
73
73
|
# if running in the test environment.
|
74
74
|
#
|
75
75
|
def index_delta
|
76
|
-
return true unless ThinkingSphinx.
|
76
|
+
return true unless ThinkingSphinx.updates_enabled? &&
|
77
|
+
ThinkingSphinx.deltas_enabled?
|
78
|
+
|
79
|
+
config = ThinkingSphinx::Configuration.new
|
80
|
+
client = Riddle::Client.new config.address, config.port
|
81
|
+
|
82
|
+
client.update(
|
83
|
+
"#{self.class.indexes.first.name}_core",
|
84
|
+
['sphinx_deleted'],
|
85
|
+
{self.id => 1}
|
86
|
+
) if self.in_core_index?
|
77
87
|
|
78
88
|
configuration = ThinkingSphinx::Configuration.new
|
79
89
|
system "indexer --config #{configuration.config_file} --rotate #{self.class.indexes.first.name}_delta"
|
@@ -28,7 +28,14 @@ module ThinkingSphinx
|
|
28
28
|
args << options
|
29
29
|
ThinkingSphinx::Search.search(*args)
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
|
+
def search_count(*args)
|
33
|
+
options = args.extract_options!
|
34
|
+
options[:class] = self
|
35
|
+
args << options
|
36
|
+
ThinkingSphinx::Search.count(*args)
|
37
|
+
end
|
38
|
+
|
32
39
|
def search_for_id(*args)
|
33
40
|
options = args.extract_options!
|
34
41
|
options[:class] = self
|
@@ -99,6 +99,10 @@ module ThinkingSphinx
|
|
99
99
|
end
|
100
100
|
result ^ 0xFFFFFFFF
|
101
101
|
end
|
102
|
+
|
103
|
+
def to_crc32s
|
104
|
+
(subclasses << self).collect { |klass| klass.to_crc32 }
|
105
|
+
end
|
102
106
|
end
|
103
107
|
end
|
104
108
|
|
@@ -118,6 +122,8 @@ module ThinkingSphinx
|
|
118
122
|
end
|
119
123
|
|
120
124
|
def toggle_deleted
|
125
|
+
return unless ThinkingSphinx.updates_enabled?
|
126
|
+
|
121
127
|
config = ThinkingSphinx::Configuration.new
|
122
128
|
client = Riddle::Client.new config.address, config.port
|
123
129
|
|
@@ -95,6 +95,10 @@ module ThinkingSphinx
|
|
95
95
|
(parent ? parent.ancestors : []) << self
|
96
96
|
end
|
97
97
|
|
98
|
+
def has_column?(column)
|
99
|
+
@reflection.klass.column_names.include?(column.to_s)
|
100
|
+
end
|
101
|
+
|
98
102
|
private
|
99
103
|
|
100
104
|
# Returns all the objects that could be currently instantiated from a
|
@@ -230,9 +230,11 @@ module ThinkingSphinx
|
|
230
230
|
"#{@model.quoted_table_name}.#{quote_column(column.__name)}"
|
231
231
|
else
|
232
232
|
associations[column].collect { |assoc|
|
233
|
+
assoc.has_column?(column.__name) ?
|
233
234
|
"#{@model.connection.quote_table_name(assoc.join.aliased_table_name)}" +
|
234
|
-
".#{quote_column(column.__name)}"
|
235
|
-
|
235
|
+
".#{quote_column(column.__name)}" :
|
236
|
+
nil
|
237
|
+
}.compact.join(', ')
|
236
238
|
end
|
237
239
|
end
|
238
240
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
class Collection < ::Array
|
3
|
+
attr_reader :total_entries, :total_pages, :current_page
|
4
|
+
|
5
|
+
def initialize(page, per_page, entries, total_entries)
|
6
|
+
@current_page, @per_page, @total_entries = page, per_page, total_entries
|
7
|
+
|
8
|
+
@total_pages = (entries / @per_page.to_f).ceil
|
9
|
+
end
|
10
|
+
|
11
|
+
def previous_page
|
12
|
+
current_page > 1 ? (current_page - 1) : nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def next_page
|
16
|
+
current_page < total_pages ? (current_page + 1): nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def offset
|
20
|
+
(current_page - 1) * @per_page
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -29,10 +29,9 @@ module ThinkingSphinx
|
|
29
29
|
# config/sphinx.yml with settings for each environment, in a similar
|
30
30
|
# fashion to database.yml - using the following keys: config_file,
|
31
31
|
# searchd_log_file, query_log_file, pid_file, searchd_file_path, port,
|
32
|
-
# allow_star, min_prefix_len, min_infix_len, mem_limit,
|
33
|
-
# morphology, charset_type, charset_table, ignore_chars,
|
34
|
-
# html_remove_elements. I think you've got
|
35
|
-
# the idea.
|
32
|
+
# allow_star, enable_star, min_prefix_len, min_infix_len, mem_limit,
|
33
|
+
# max_matches, # morphology, charset_type, charset_table, ignore_chars,
|
34
|
+
# html_strip, # html_remove_elements. I think you've got the idea.
|
36
35
|
#
|
37
36
|
# Each setting in the YAML file is optional - so only put in the ones you
|
38
37
|
# want to change.
|
@@ -43,10 +42,10 @@ module ThinkingSphinx
|
|
43
42
|
#
|
44
43
|
class Configuration
|
45
44
|
attr_accessor :config_file, :searchd_log_file, :query_log_file,
|
46
|
-
:pid_file, :searchd_file_path, :address, :port, :
|
47
|
-
:min_prefix_len, :min_infix_len, :mem_limit, :max_matches,
|
48
|
-
:charset_type, :charset_table, :ignore_chars, :html_strip,
|
49
|
-
:html_remove_elements, :app_root
|
45
|
+
:pid_file, :searchd_file_path, :address, :port, :enable_star,
|
46
|
+
:allow_star, :min_prefix_len, :min_infix_len, :mem_limit, :max_matches,
|
47
|
+
:morphology, :charset_type, :charset_table, :ignore_chars, :html_strip,
|
48
|
+
:html_remove_elements, :database_yml_file, :app_root
|
50
49
|
|
51
50
|
attr_reader :environment
|
52
51
|
|
@@ -58,6 +57,7 @@ module ThinkingSphinx
|
|
58
57
|
self.app_root = Merb.root if defined?(Merb)
|
59
58
|
self.app_root ||= app_root
|
60
59
|
|
60
|
+
self.database_yml_file = "#{self.app_root}/config/database.yml"
|
61
61
|
self.config_file = "#{self.app_root}/config/#{environment}.sphinx.conf"
|
62
62
|
self.searchd_log_file = "#{self.app_root}/log/searchd.log"
|
63
63
|
self.query_log_file = "#{self.app_root}/log/searchd.query.log"
|
@@ -66,8 +66,9 @@ module ThinkingSphinx
|
|
66
66
|
self.address = "127.0.0.1"
|
67
67
|
self.port = 3312
|
68
68
|
self.allow_star = false
|
69
|
-
self.
|
70
|
-
self.
|
69
|
+
self.enable_star = false
|
70
|
+
self.min_prefix_len = nil
|
71
|
+
self.min_infix_len = nil
|
71
72
|
self.mem_limit = "64M"
|
72
73
|
self.max_matches = 1000
|
73
74
|
self.morphology = "stem_en"
|
@@ -95,9 +96,8 @@ module ThinkingSphinx
|
|
95
96
|
# indexer and searchd configuration, and sources and indexes details.
|
96
97
|
#
|
97
98
|
def build(file_path=nil)
|
98
|
-
load_models
|
99
99
|
file_path ||= "#{self.config_file}"
|
100
|
-
database_confs = YAML::load(ERB.new(IO.read("#{
|
100
|
+
database_confs = YAML::load(ERB.new(IO.read("#{self.database_yml_file}")).result)
|
101
101
|
database_confs.symbolize_keys!
|
102
102
|
database_conf = database_confs[environment.to_sym]
|
103
103
|
database_conf.symbolize_keys!
|
@@ -122,22 +122,24 @@ searchd
|
|
122
122
|
}
|
123
123
|
CONFIG
|
124
124
|
|
125
|
-
ThinkingSphinx.indexed_models.
|
125
|
+
ThinkingSphinx.indexed_models.each_with_index do |model, model_index|
|
126
126
|
model = model.constantize
|
127
127
|
sources = []
|
128
128
|
delta_sources = []
|
129
129
|
prefixed_fields = []
|
130
130
|
infixed_fields = []
|
131
131
|
|
132
|
-
model.indexes.each_with_index do |index, i|
|
133
|
-
file.write index.to_config(i, database_conf, charset_type)
|
132
|
+
model.indexes.select { |index| index.model == model }.each_with_index do |index, i|
|
133
|
+
file.write index.to_config(model, i, database_conf, charset_type, model_index)
|
134
134
|
|
135
135
|
create_array_accum if index.adapter == :postgres
|
136
|
-
sources << "#{
|
137
|
-
delta_sources << "#{
|
136
|
+
sources << "#{ThinkingSphinx::Index.name(model)}_#{i}_core"
|
137
|
+
delta_sources << "#{ThinkingSphinx::Index.name(model)}_#{i}_delta" if index.delta?
|
138
138
|
end
|
139
139
|
|
140
|
-
|
140
|
+
next if sources.empty?
|
141
|
+
|
142
|
+
source_list = sources.collect { |s| "source = #{s}" }.join("\n")
|
141
143
|
delta_list = delta_sources.collect { |s| "source = #{s}" }.join("\n")
|
142
144
|
|
143
145
|
file.write core_index_for_model(model, source_list)
|
@@ -167,8 +169,7 @@ searchd
|
|
167
169
|
begin
|
168
170
|
model_name.camelize.constantize
|
169
171
|
rescue LoadError
|
170
|
-
model_name.gsub!(/.*[\/\\]/, '')
|
171
|
-
retry
|
172
|
+
model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
|
172
173
|
rescue NameError
|
173
174
|
next
|
174
175
|
end
|
@@ -194,10 +195,10 @@ searchd
|
|
194
195
|
def core_index_for_model(model, sources)
|
195
196
|
output = <<-INDEX
|
196
197
|
|
197
|
-
index #{
|
198
|
+
index #{ThinkingSphinx::Index.name(model)}_core
|
198
199
|
{
|
199
200
|
#{sources}
|
200
|
-
path = #{self.searchd_file_path}/#{
|
201
|
+
path = #{self.searchd_file_path}/#{ThinkingSphinx::Index.name(model)}_core
|
201
202
|
charset_type = #{self.charset_type}
|
202
203
|
INDEX
|
203
204
|
|
@@ -209,20 +210,30 @@ INDEX
|
|
209
210
|
output += " ignore_chars = #{self.ignore_chars}\n" unless self.ignore_chars.nil?
|
210
211
|
|
211
212
|
if self.allow_star
|
213
|
+
# Ye Olde way of turning on enable_star
|
212
214
|
output += " enable_star = 1\n"
|
213
215
|
output += " min_prefix_len = #{self.min_prefix_len}\n"
|
214
|
-
|
216
|
+
else
|
217
|
+
# New, better way of turning on enable_star - thanks to James Healy
|
218
|
+
output += " enable_star = 1\n" if self.enable_star
|
219
|
+
output += " min_prefix_len = #{self.min_prefix_len}\n" unless self.min_prefix_len.nil?
|
220
|
+
output += " min_infix_len = #{self.min_infix_len}\n" unless self.min_infix_len.nil?
|
215
221
|
end
|
216
222
|
|
223
|
+
|
217
224
|
output += " html_strip = 1\n" if self.html_strip
|
218
225
|
output += " html_remove_elements = #{self.html_remove_elements}\n" unless self.html_remove_elements.blank?
|
219
226
|
|
220
227
|
unless model.indexes.collect(&:prefix_fields).flatten.empty?
|
221
|
-
output += " prefix_fields = #{model.indexes.collect(&:prefix_fields).flatten.join(', ')}\n"
|
228
|
+
output += " prefix_fields = #{model.indexes.collect(&:prefix_fields).flatten.map(&:unique_name).join(', ')}\n"
|
229
|
+
else
|
230
|
+
output += " prefix_fields = _\n" unless model.indexes.collect(&:infix_fields).flatten.empty?
|
222
231
|
end
|
223
232
|
|
224
233
|
unless model.indexes.collect(&:infix_fields).flatten.empty?
|
225
|
-
output += " infix_fields = #{model.indexes.collect(&:infix_fields).flatten.join(', ')}\n"
|
234
|
+
output += " infix_fields = #{model.indexes.collect(&:infix_fields).flatten.map(&:unique_name).join(', ')}\n"
|
235
|
+
else
|
236
|
+
output += " infix_fields = -\n" unless model.indexes.collect(&:prefix_fields).flatten.empty?
|
226
237
|
end
|
227
238
|
|
228
239
|
output + "}\n"
|
@@ -230,22 +241,22 @@ INDEX
|
|
230
241
|
|
231
242
|
def delta_index_for_model(model, sources)
|
232
243
|
<<-INDEX
|
233
|
-
index #{
|
244
|
+
index #{ThinkingSphinx::Index.name(model)}_delta : #{ThinkingSphinx::Index.name(model)}_core
|
234
245
|
{
|
235
246
|
#{sources}
|
236
|
-
path = #{self.searchd_file_path}/#{
|
247
|
+
path = #{self.searchd_file_path}/#{ThinkingSphinx::Index.name(model)}_delta
|
237
248
|
}
|
238
249
|
INDEX
|
239
250
|
end
|
240
251
|
|
241
252
|
def distributed_index_for_model(model)
|
242
|
-
sources = ["local = #{
|
253
|
+
sources = ["local = #{ThinkingSphinx::Index.name(model)}_core"]
|
243
254
|
if model.indexes.any? { |index| index.delta? }
|
244
|
-
sources << "local = #{
|
255
|
+
sources << "local = #{ThinkingSphinx::Index.name(model)}_delta"
|
245
256
|
end
|
246
257
|
|
247
258
|
<<-INDEX
|
248
|
-
index #{
|
259
|
+
index #{ThinkingSphinx::Index.name(model)}
|
249
260
|
{
|
250
261
|
type = distributed
|
251
262
|
#{ sources.join("\n ") }
|
@@ -178,13 +178,17 @@ module ThinkingSphinx
|
|
178
178
|
# figure out how to correctly reference a column in SQL.
|
179
179
|
#
|
180
180
|
def column_with_prefix(column)
|
181
|
-
if
|
181
|
+
if column.is_string?
|
182
|
+
column.__name
|
183
|
+
elsif associations[column].empty?
|
182
184
|
"#{@model.quoted_table_name}.#{quote_column(column.__name)}"
|
183
185
|
else
|
184
186
|
associations[column].collect { |assoc|
|
187
|
+
assoc.has_column?(column.__name) ?
|
185
188
|
"#{@model.connection.quote_table_name(assoc.join.aliased_table_name)}" +
|
186
|
-
".#{quote_column(column.__name)}"
|
187
|
-
|
189
|
+
".#{quote_column(column.__name)}" :
|
190
|
+
nil
|
191
|
+
}.compact.join(', ')
|
188
192
|
end
|
189
193
|
end
|
190
194
|
|
@@ -194,5 +198,9 @@ module ThinkingSphinx
|
|
194
198
|
def is_many?
|
195
199
|
associations.values.flatten.any? { |assoc| assoc.is_many? }
|
196
200
|
end
|
201
|
+
|
202
|
+
def is_string?
|
203
|
+
columns.all? { |col| col.is_string? }
|
204
|
+
end
|
197
205
|
end
|
198
206
|
end
|
@@ -81,8 +81,7 @@ module ThinkingSphinx
|
|
81
81
|
def indexes(*args)
|
82
82
|
options = args.extract_options!
|
83
83
|
args.each do |columns|
|
84
|
-
|
85
|
-
fields << Field.new(columns, options)
|
84
|
+
fields << Field.new(FauxColumn.coerce(columns), options)
|
86
85
|
|
87
86
|
if fields.last.sortable
|
88
87
|
attributes << Attribute.new(
|
@@ -137,23 +136,7 @@ module ThinkingSphinx
|
|
137
136
|
def has(*args)
|
138
137
|
options = args.extract_options!
|
139
138
|
args.each do |columns|
|
140
|
-
|
141
|
-
when Symbol, String
|
142
|
-
FauxColumn.new(columns)
|
143
|
-
when Array
|
144
|
-
columns.collect { |col|
|
145
|
-
case col
|
146
|
-
when Symbol, String
|
147
|
-
FauxColumn.new(col)
|
148
|
-
else
|
149
|
-
col
|
150
|
-
end
|
151
|
-
}
|
152
|
-
else
|
153
|
-
columns
|
154
|
-
end
|
155
|
-
|
156
|
-
attributes << Attribute.new(columns, options)
|
139
|
+
attributes << Attribute.new(FauxColumn.coerce(columns), options)
|
157
140
|
end
|
158
141
|
end
|
159
142
|
alias_method :attribute, :has
|
@@ -15,6 +15,19 @@ module ThinkingSphinx
|
|
15
15
|
@stack = stack
|
16
16
|
end
|
17
17
|
|
18
|
+
def self.coerce(columns)
|
19
|
+
case columns
|
20
|
+
when Symbol, String
|
21
|
+
FauxColumn.new(columns)
|
22
|
+
when Array
|
23
|
+
columns.collect { |col| FauxColumn.coerce(col) }
|
24
|
+
when FauxColumn
|
25
|
+
columns
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
18
31
|
# Can't use normal method name, as that could be an association or
|
19
32
|
# column name.
|
20
33
|
#
|
@@ -38,6 +38,10 @@ module ThinkingSphinx
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def name
|
41
|
+
self.class.name(@model)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.name(model)
|
41
45
|
model.name.underscore.tr(':/\\', '_')
|
42
46
|
end
|
43
47
|
|
@@ -46,8 +50,9 @@ module ThinkingSphinx
|
|
46
50
|
File.size?("#{config.searchd_file_path}/#{self.name}_#{part}.spa").nil?
|
47
51
|
end
|
48
52
|
|
49
|
-
def to_config(index, database_conf, charset_type)
|
53
|
+
def to_config(model, index, database_conf, charset_type, offset)
|
50
54
|
# Set up associations and joins
|
55
|
+
add_internal_attributes
|
51
56
|
link!
|
52
57
|
|
53
58
|
attr_sources = attributes.collect { |attrib|
|
@@ -65,20 +70,21 @@ module ThinkingSphinx
|
|
65
70
|
|
66
71
|
config = <<-SOURCE
|
67
72
|
|
68
|
-
source #{
|
73
|
+
source #{self.class.name(model)}_#{index}_core
|
69
74
|
{
|
70
75
|
type = #{db_adapter}
|
71
76
|
sql_host = #{database_conf[:host] || "localhost"}
|
72
|
-
sql_user = #{database_conf[:username]}
|
73
|
-
sql_pass = #{database_conf[:password]}
|
77
|
+
sql_user = #{database_conf[:username] || database_conf[:user]}
|
78
|
+
sql_pass = #{(database_conf[:password] || "").gsub('#', '\#')}
|
74
79
|
sql_db = #{database_conf[:database]}
|
80
|
+
#{"sql_sock = #{database_conf[:socket]}" unless database_conf[:socket].blank? }
|
75
81
|
|
76
82
|
sql_query_pre = #{charset_type == "utf-8" && adapter == :mysql ? "SET NAMES utf8" : ""}
|
77
83
|
#{"sql_query_pre = SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}" if @options[:group_concat_max_len]}
|
78
84
|
sql_query_pre = #{to_sql_query_pre}
|
79
|
-
sql_query = #{to_sql.gsub(/\n/, ' ')}
|
80
|
-
sql_query_range = #{to_sql_query_range}
|
81
|
-
sql_query_info = #{to_sql_query_info}
|
85
|
+
sql_query = #{to_sql(:offset => offset).gsub(/\n/, ' ')}
|
86
|
+
sql_query_range = #{to_sql_query_range :offset => offset}
|
87
|
+
sql_query_info = #{to_sql_query_info(offset)}
|
82
88
|
#{attr_sources}
|
83
89
|
}
|
84
90
|
SOURCE
|
@@ -86,13 +92,13 @@ sql_query_info = #{to_sql_query_info}
|
|
86
92
|
if delta?
|
87
93
|
config += <<-SOURCE
|
88
94
|
|
89
|
-
source #{
|
95
|
+
source #{self.class.name(model)}_#{index}_delta : #{self.class.name(model)}_#{index}_core
|
90
96
|
{
|
91
97
|
sql_query_pre =
|
92
98
|
sql_query_pre = #{charset_type == "utf-8" && adapter == :mysql ? "SET NAMES utf8" : ""}
|
93
99
|
#{"sql_query_pre = SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}" if @options[:group_concat_max_len]}
|
94
|
-
sql_query = #{to_sql(:delta => true).gsub(/\n/, ' ')}
|
95
|
-
sql_query_range = #{to_sql_query_range :delta => true}
|
100
|
+
sql_query = #{to_sql(:delta => true, :offset => offset).gsub(/\n/, ' ')}
|
101
|
+
sql_query_range = #{to_sql_query_range :offset => offset, :delta => true}
|
96
102
|
}
|
97
103
|
SOURCE
|
98
104
|
end
|
@@ -149,16 +155,18 @@ sql_query_range = #{to_sql_query_range :delta => true}
|
|
149
155
|
where_clause << " AND " << @conditions.join(" AND ")
|
150
156
|
end
|
151
157
|
|
158
|
+
unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
|
159
|
+
|
152
160
|
sql = <<-SQL
|
153
161
|
SELECT #{ (
|
154
|
-
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
|
162
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
|
155
163
|
@fields.collect { |field| field.to_select_sql } +
|
156
164
|
@attributes.collect { |attribute| attribute.to_select_sql }
|
157
165
|
).join(", ") }
|
158
166
|
FROM #{ @model.table_name }
|
159
167
|
#{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
|
160
|
-
WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
|
161
|
-
AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
|
168
|
+
WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} >= $start
|
169
|
+
AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} <= $end
|
162
170
|
#{ where_clause }
|
163
171
|
GROUP BY #{ (
|
164
172
|
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
|
@@ -177,9 +185,9 @@ GROUP BY #{ (
|
|
177
185
|
# Simple helper method for the query info SQL - which is a statement that
|
178
186
|
# returns the single row for a corresponding id.
|
179
187
|
#
|
180
|
-
def to_sql_query_info
|
188
|
+
def to_sql_query_info(offset)
|
181
189
|
"SELECT * FROM #{@model.quoted_table_name} WHERE " +
|
182
|
-
" #{quote_column(@model.primary_key)} = $id"
|
190
|
+
" #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
|
183
191
|
end
|
184
192
|
|
185
193
|
# Simple helper method for the query range SQL - which is a statement that
|
@@ -187,8 +195,10 @@ GROUP BY #{ (
|
|
187
195
|
# so pass in :delta => true to get the delta version of the SQL.
|
188
196
|
#
|
189
197
|
def to_sql_query_range(options={})
|
190
|
-
|
191
|
-
|
198
|
+
unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
|
199
|
+
|
200
|
+
min_statement = "MIN(#{quote_column(@model.primary_key)} #{unique_id_expr})"
|
201
|
+
max_statement = "MAX(#{quote_column(@model.primary_key)} #{unique_id_expr})"
|
192
202
|
|
193
203
|
# Fix to handle Sphinx PostgreSQL bug (it doesn't like NULLs or 0's)
|
194
204
|
if adapter == :postgres
|
@@ -265,17 +275,6 @@ GROUP BY #{ (
|
|
265
275
|
@conditions = builder.conditions
|
266
276
|
@delta = builder.properties[:delta]
|
267
277
|
@options = builder.properties.except(:delta)
|
268
|
-
|
269
|
-
@attributes << Attribute.new(
|
270
|
-
FauxColumn.new(@model.to_crc32.to_s),
|
271
|
-
:type => :integer,
|
272
|
-
:as => :class_crc
|
273
|
-
)
|
274
|
-
@attributes << Attribute.new(
|
275
|
-
FauxColumn.new("0"),
|
276
|
-
:type => :integer,
|
277
|
-
:as => :sphinx_deleted
|
278
|
-
)
|
279
278
|
end
|
280
279
|
|
281
280
|
# Returns all associations used amongst all the fields and attributes.
|
@@ -335,5 +334,43 @@ GROUP BY #{ (
|
|
335
334
|
val ? '1' : '0'
|
336
335
|
end
|
337
336
|
end
|
337
|
+
|
338
|
+
def crc_column
|
339
|
+
if adapter == :postgres
|
340
|
+
@model.to_crc32.to_s
|
341
|
+
elsif @model.column_names.include?(@model.inheritance_column)
|
342
|
+
"IFNULL(CRC32(#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}), #{@model.to_crc32.to_s})"
|
343
|
+
else
|
344
|
+
@model.to_crc32.to_s
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def add_internal_attributes
|
349
|
+
@attributes << Attribute.new(
|
350
|
+
FauxColumn.new(:id),
|
351
|
+
:type => :integer,
|
352
|
+
:as => :sphinx_internal_id
|
353
|
+
) unless @attributes.detect { |attr| attr.alias == :sphinx_internal_id }
|
354
|
+
|
355
|
+
@attributes << Attribute.new(
|
356
|
+
FauxColumn.new(crc_column),
|
357
|
+
:type => :integer,
|
358
|
+
:as => :class_crc
|
359
|
+
) unless @attributes.detect { |attr| attr.alias == :class_crc }
|
360
|
+
|
361
|
+
@attributes << Attribute.new(
|
362
|
+
FauxColumn.new("'" + (@model.send(:subclasses).collect { |klass|
|
363
|
+
klass.to_crc32.to_s
|
364
|
+
} << @model.to_crc32.to_s).join(",") + "'"),
|
365
|
+
:type => :multi,
|
366
|
+
:as => :subclass_crcs
|
367
|
+
) unless @attributes.detect { |attr| attr.alias == :subclass_crcs }
|
368
|
+
|
369
|
+
@attributes << Attribute.new(
|
370
|
+
FauxColumn.new("0"),
|
371
|
+
:type => :integer,
|
372
|
+
:as => :sphinx_deleted
|
373
|
+
) unless @attributes.detect { |attr| attr.alias == :sphinx_deleted }
|
374
|
+
end
|
338
375
|
end
|
339
376
|
end
|