thinking-sphinx 3.0.0.pre → 3.0.0.rc
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/Gemfile +4 -1
- data/HISTORY +16 -0
- data/README.textile +41 -23
- data/lib/thinking_sphinx.rb +9 -0
- data/lib/thinking_sphinx/active_record.rb +1 -0
- data/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb +20 -3
- data/lib/thinking_sphinx/active_record/base.rb +15 -2
- data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +2 -2
- data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
- data/lib/thinking_sphinx/active_record/field.rb +5 -0
- data/lib/thinking_sphinx/active_record/index.rb +10 -2
- data/lib/thinking_sphinx/active_record/interpreter.rb +9 -0
- data/lib/thinking_sphinx/active_record/property.rb +4 -0
- data/lib/thinking_sphinx/active_record/property_query.rb +112 -0
- data/lib/thinking_sphinx/active_record/sql_source.rb +10 -8
- data/lib/thinking_sphinx/active_record/sql_source/template.rb +3 -1
- data/lib/thinking_sphinx/configuration.rb +21 -24
- data/lib/thinking_sphinx/connection.rb +71 -0
- data/lib/thinking_sphinx/core.rb +1 -0
- data/lib/thinking_sphinx/core/field.rb +9 -0
- data/lib/thinking_sphinx/core/index.rb +8 -3
- data/lib/thinking_sphinx/deltas.rb +3 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
- data/lib/thinking_sphinx/excerpter.rb +1 -1
- data/lib/thinking_sphinx/frameworks.rb +9 -0
- data/lib/thinking_sphinx/frameworks/plain.rb +8 -0
- data/lib/thinking_sphinx/frameworks/rails.rb +9 -0
- data/lib/thinking_sphinx/index.rb +5 -1
- data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +2 -2
- data/lib/thinking_sphinx/real_time/field.rb +2 -0
- data/lib/thinking_sphinx/real_time/property.rb +2 -0
- data/lib/thinking_sphinx/scopes.rb +6 -0
- data/lib/thinking_sphinx/search.rb +4 -0
- data/lib/thinking_sphinx/search/batch_inquirer.rb +1 -9
- data/lib/thinking_sphinx/search/merger.rb +3 -1
- data/lib/thinking_sphinx/sinatra.rb +5 -0
- data/lib/thinking_sphinx/test.rb +2 -2
- data/spec/acceptance/index_options_spec.rb +87 -0
- data/spec/acceptance/searching_within_a_model_spec.rb +7 -0
- data/spec/acceptance/specifying_sql_spec.rb +277 -0
- data/spec/acceptance/support/sphinx_controller.rb +4 -3
- data/spec/fixtures/database.yml +4 -0
- data/spec/internal/app/indices/admin_person_index.rb +3 -0
- data/spec/internal/app/models/admin/person.rb +3 -0
- data/spec/internal/app/models/book.rb +2 -0
- data/spec/internal/app/models/genre.rb +3 -0
- data/spec/internal/db/schema.rb +14 -0
- data/spec/thinking_sphinx/active_record/base_spec.rb +20 -14
- data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +6 -5
- data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +6 -5
- data/spec/thinking_sphinx/active_record/index_spec.rb +1 -1
- data/spec/thinking_sphinx/active_record/sql_source_spec.rb +35 -27
- data/spec/thinking_sphinx/configuration_spec.rb +8 -45
- data/spec/thinking_sphinx/deltas/default_delta_spec.rb +4 -5
- data/spec/thinking_sphinx/deltas_spec.rb +6 -0
- data/spec/thinking_sphinx/excerpter_spec.rb +1 -2
- data/spec/thinking_sphinx/index_spec.rb +23 -10
- data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +4 -4
- data/spec/thinking_sphinx/scopes_spec.rb +7 -0
- data/thinking-sphinx.gemspec +2 -3
- metadata +66 -26
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'acceptance/spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Index options' do
|
|
4
|
+
let(:index) { ThinkingSphinx::ActiveRecord::Index.new(:article) }
|
|
5
|
+
|
|
6
|
+
%w( infix prefix ).each do |type|
|
|
7
|
+
context "all fields are #{type}ed" do
|
|
8
|
+
before :each do
|
|
9
|
+
index.definition_block = Proc.new {
|
|
10
|
+
indexes title
|
|
11
|
+
set_property "min_#{type}_len".to_sym => 3
|
|
12
|
+
}
|
|
13
|
+
index.render
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "keeps #{type}_fields blank" do
|
|
17
|
+
index.send("#{type}_fields").should be_nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "sets min_#{type}_len" do
|
|
21
|
+
index.send("min_#{type}_len").should == 3
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "some fields are #{type}ed" do
|
|
26
|
+
before :each do
|
|
27
|
+
index.definition_block = Proc.new {
|
|
28
|
+
indexes title, "#{type}es".to_sym => true
|
|
29
|
+
indexes content
|
|
30
|
+
set_property "min_#{type}_len".to_sym => 3
|
|
31
|
+
}
|
|
32
|
+
index.render
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "#{type}_fields should contain the field" do
|
|
36
|
+
index.send("#{type}_fields").should == 'title'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "sets min_#{type}_len" do
|
|
40
|
+
index.send("min_#{type}_len").should == 3
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "multiple source definitions" do
|
|
46
|
+
before :each do
|
|
47
|
+
index.definition_block = Proc.new {
|
|
48
|
+
define_source do
|
|
49
|
+
indexes title
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
define_source do
|
|
53
|
+
indexes title, content
|
|
54
|
+
end
|
|
55
|
+
}
|
|
56
|
+
index.render
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "stores each source definition" do
|
|
60
|
+
index.sources.length.should == 2
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "treats each source as separate" do
|
|
64
|
+
index.sources.first.fields.length.should == 2
|
|
65
|
+
index.sources.last.fields.length.should == 3
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'wordcount fields and attributes' do
|
|
70
|
+
before :each do
|
|
71
|
+
index.definition_block = Proc.new {
|
|
72
|
+
indexes title, :wordcount => true
|
|
73
|
+
|
|
74
|
+
has content, :type => :wordcount
|
|
75
|
+
}
|
|
76
|
+
index.render
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "declares wordcount fields" do
|
|
80
|
+
index.sources.first.sql_field_str2wordcount.should == ['title']
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "declares wordcount attributes" do
|
|
84
|
+
index.sources.first.sql_attr_str2wordcount.should == ['content']
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -40,6 +40,13 @@ describe 'Searching within a model', :live => true do
|
|
|
40
40
|
articles = Article.search('pancake', :indices => ['stemmed_article_core'])
|
|
41
41
|
articles.to_a.should == [article]
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
it "can search on namespaced models" do
|
|
45
|
+
person = Admin::Person.create :name => 'James Bond'
|
|
46
|
+
index
|
|
47
|
+
|
|
48
|
+
Admin::Person.search('Bond').to_a.should == [person]
|
|
49
|
+
end
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
describe 'Searching within a model with a realtime index', :live => true do
|
|
@@ -24,6 +24,18 @@ describe 'specifying SQL for index definitions' do
|
|
|
24
24
|
query.should match(/LEFT OUTER JOIN .articles./)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
it "handles has-many :through joins" do
|
|
28
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
|
|
29
|
+
index.definition_block = Proc.new {
|
|
30
|
+
indexes tags.name
|
|
31
|
+
}
|
|
32
|
+
index.render
|
|
33
|
+
|
|
34
|
+
query = index.sources.first.sql_query
|
|
35
|
+
query.should match(/LEFT OUTER JOIN .taggings./)
|
|
36
|
+
query.should match(/LEFT OUTER JOIN .tags./)
|
|
37
|
+
end
|
|
38
|
+
|
|
27
39
|
it "handles GROUP BY clauses" do
|
|
28
40
|
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
|
|
29
41
|
index.definition_block = Proc.new {
|
|
@@ -59,4 +71,269 @@ describe 'specifying SQL for index definitions' do
|
|
|
59
71
|
|
|
60
72
|
index.sources.first.sql_attr_multi.should == ['uint tag_ids from field']
|
|
61
73
|
end
|
|
74
|
+
|
|
75
|
+
it "provides the sanitize_sql helper within the index definition block" do
|
|
76
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:article)
|
|
77
|
+
index.definition_block = Proc.new {
|
|
78
|
+
indexes title
|
|
79
|
+
where sanitize_sql(["title != ?", 'secret'])
|
|
80
|
+
}
|
|
81
|
+
index.render
|
|
82
|
+
|
|
83
|
+
query = index.sources.first.sql_query
|
|
84
|
+
query.should match(/WHERE .+title != 'secret'.+ GROUP BY/)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe 'separate queries for MVAs' do
|
|
89
|
+
let(:index) { ThinkingSphinx::ActiveRecord::Index.new(:article) }
|
|
90
|
+
let(:count) { ThinkingSphinx::Configuration.instance.indices.count }
|
|
91
|
+
let(:source) { index.sources.first }
|
|
92
|
+
|
|
93
|
+
it "generates an appropriate SQL query for an MVA" do
|
|
94
|
+
index.definition_block = Proc.new {
|
|
95
|
+
indexes title
|
|
96
|
+
has taggings.tag_id, :as => :tag_ids, :source => :query
|
|
97
|
+
}
|
|
98
|
+
index.render
|
|
99
|
+
|
|
100
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
|
101
|
+
attribute[/tag_ids/]
|
|
102
|
+
}
|
|
103
|
+
declaration, query = attribute.split(/;\s+/)
|
|
104
|
+
|
|
105
|
+
declaration.should == 'uint tag_ids from query'
|
|
106
|
+
query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings.\s?$/)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "generates a SQL query with joins when appropriate for MVAs" do
|
|
110
|
+
index.definition_block = Proc.new {
|
|
111
|
+
indexes title
|
|
112
|
+
has taggings.tag.id, :as => :tag_ids, :source => :query
|
|
113
|
+
}
|
|
114
|
+
index.render
|
|
115
|
+
|
|
116
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
|
117
|
+
attribute[/tag_ids/]
|
|
118
|
+
}
|
|
119
|
+
declaration, query = attribute.split(/;\s+/)
|
|
120
|
+
|
|
121
|
+
declaration.should == 'uint tag_ids from query'
|
|
122
|
+
query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s?$/)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "respects has_many :through joins for MVA queries" do
|
|
126
|
+
index.definition_block = Proc.new {
|
|
127
|
+
indexes title
|
|
128
|
+
has tags.id, :as => :tag_ids, :source => :query
|
|
129
|
+
}
|
|
130
|
+
index.render
|
|
131
|
+
|
|
132
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
|
133
|
+
attribute[/tag_ids/]
|
|
134
|
+
}
|
|
135
|
+
declaration, query = attribute.split(/;\s+/)
|
|
136
|
+
|
|
137
|
+
declaration.should == 'uint tag_ids from query'
|
|
138
|
+
query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s?$/)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "can handle multiple joins for MVA queries" do
|
|
142
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:user)
|
|
143
|
+
index.definition_block = Proc.new {
|
|
144
|
+
indexes name
|
|
145
|
+
has articles.tags.id, :as => :tag_ids, :source => :query
|
|
146
|
+
}
|
|
147
|
+
index.render
|
|
148
|
+
source = index.sources.first
|
|
149
|
+
|
|
150
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
|
151
|
+
attribute[/tag_ids/]
|
|
152
|
+
}
|
|
153
|
+
declaration, query = attribute.split(/;\s+/)
|
|
154
|
+
|
|
155
|
+
declaration.should == 'uint tag_ids from query'
|
|
156
|
+
query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s?$/)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "can handle HABTM joins for MVA queries" do
|
|
160
|
+
pending "Efficient HABTM queries are tricky."
|
|
161
|
+
# We don't really have any need for other tables, but that doesn't lend
|
|
162
|
+
# itself nicely to Thinking Sphinx's DSL, nor ARel SQL generation. This is
|
|
163
|
+
# a low priority - manual SQL queries for this situation may work better.
|
|
164
|
+
|
|
165
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:book)
|
|
166
|
+
index.definition_block = Proc.new {
|
|
167
|
+
indexes title
|
|
168
|
+
has genres.id, :as => :genre_ids, :source => :query
|
|
169
|
+
}
|
|
170
|
+
index.render
|
|
171
|
+
source = index.sources.first
|
|
172
|
+
|
|
173
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
|
174
|
+
attribute[/genre_ids/]
|
|
175
|
+
}
|
|
176
|
+
declaration, query = attribute.split(/;\s+/)
|
|
177
|
+
|
|
178
|
+
declaration.should == 'uint genre_ids from query'
|
|
179
|
+
query.should match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .genres.\..id. AS .genre_ids. FROM .books_genres. INNER JOIN .genres. ON .genres.\..id. = .books_genres.\..genre_id.\s?$/)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it "generates an appropriate range SQL queries for an MVA" do
|
|
183
|
+
index.definition_block = Proc.new {
|
|
184
|
+
indexes title
|
|
185
|
+
has taggings.tag_id, :as => :tag_ids, :source => :ranged_query
|
|
186
|
+
}
|
|
187
|
+
index.render
|
|
188
|
+
|
|
189
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
|
190
|
+
attribute[/tag_ids/]
|
|
191
|
+
}
|
|
192
|
+
declaration, query, range = attribute.split(/;\s+/)
|
|
193
|
+
|
|
194
|
+
declaration.should == 'uint tag_ids from ranged-query'
|
|
195
|
+
query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings. \s?WHERE \(.taggings.\..article_id. >= \$start\) AND \(.taggings.\..article_id. <= \$end\)$/)
|
|
196
|
+
range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "generates a SQL query with joins when appropriate for MVAs" do
|
|
200
|
+
index.definition_block = Proc.new {
|
|
201
|
+
indexes title
|
|
202
|
+
has taggings.tag.id, :as => :tag_ids, :source => :ranged_query
|
|
203
|
+
}
|
|
204
|
+
index.render
|
|
205
|
+
|
|
206
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
|
207
|
+
attribute[/tag_ids/]
|
|
208
|
+
}
|
|
209
|
+
declaration, query, range = attribute.split(/;\s+/)
|
|
210
|
+
|
|
211
|
+
declaration.should == 'uint tag_ids from ranged-query'
|
|
212
|
+
query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. >= \$start\) AND \(.taggings.\..article_id. <= \$end\)$/)
|
|
213
|
+
range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it "respects custom SQL snippets as the query value" do
|
|
217
|
+
index.definition_block = Proc.new {
|
|
218
|
+
indexes title
|
|
219
|
+
has 'My Custom SQL Query', :as => :tag_ids, :source => :query,
|
|
220
|
+
:type => :integer, :multi => true
|
|
221
|
+
}
|
|
222
|
+
index.render
|
|
223
|
+
|
|
224
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
|
225
|
+
attribute[/tag_ids/]
|
|
226
|
+
}
|
|
227
|
+
declaration, query = attribute.split(/;\s+/)
|
|
228
|
+
|
|
229
|
+
declaration.should == 'uint tag_ids from query'
|
|
230
|
+
query.should == 'My Custom SQL Query'
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it "respects custom SQL snippets as the ranged query value" do
|
|
234
|
+
index.definition_block = Proc.new {
|
|
235
|
+
indexes title
|
|
236
|
+
has 'My Custom SQL Query; And a Range', :as => :tag_ids,
|
|
237
|
+
:source => :ranged_query, :type => :integer, :multi => true
|
|
238
|
+
}
|
|
239
|
+
index.render
|
|
240
|
+
|
|
241
|
+
attribute = source.sql_attr_multi.detect { |attribute|
|
|
242
|
+
attribute[/tag_ids/]
|
|
243
|
+
}
|
|
244
|
+
declaration, query, range = attribute.split(/;\s+/)
|
|
245
|
+
|
|
246
|
+
declaration.should == 'uint tag_ids from ranged-query'
|
|
247
|
+
query.should == 'My Custom SQL Query'
|
|
248
|
+
range.should == 'And a Range'
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
describe 'separate queries for field' do
|
|
253
|
+
let(:index) { ThinkingSphinx::ActiveRecord::Index.new(:article) }
|
|
254
|
+
let(:count) { ThinkingSphinx::Configuration.instance.indices.count }
|
|
255
|
+
let(:source) { index.sources.first }
|
|
256
|
+
|
|
257
|
+
it "generates a SQL query with joins when appropriate for MVF" do
|
|
258
|
+
index.definition_block = Proc.new {
|
|
259
|
+
indexes taggings.tag.name, :as => :tags, :source => :query
|
|
260
|
+
}
|
|
261
|
+
index.render
|
|
262
|
+
|
|
263
|
+
field = source.sql_joined_field.detect { |field| field[/tags/] }
|
|
264
|
+
declaration, query = field.split(/;\s+/)
|
|
265
|
+
|
|
266
|
+
declaration.should == 'tags from query'
|
|
267
|
+
query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. ORDER BY .taggings.\..article_id. ASC\s?$/)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "respects has_many :through joins for MVF queries" do
|
|
271
|
+
index.definition_block = Proc.new {
|
|
272
|
+
indexes tags.name, :as => :tags, :source => :query
|
|
273
|
+
}
|
|
274
|
+
index.render
|
|
275
|
+
|
|
276
|
+
field = source.sql_joined_field.detect { |field| field[/tags/] }
|
|
277
|
+
declaration, query = field.split(/;\s+/)
|
|
278
|
+
|
|
279
|
+
declaration.should == 'tags from query'
|
|
280
|
+
query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. ORDER BY .taggings.\..article_id. ASC\s?$/)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "can handle multiple joins for MVF queries" do
|
|
284
|
+
index = ThinkingSphinx::ActiveRecord::Index.new(:user)
|
|
285
|
+
index.definition_block = Proc.new {
|
|
286
|
+
indexes articles.tags.name, :as => :tags, :source => :query
|
|
287
|
+
}
|
|
288
|
+
index.render
|
|
289
|
+
source = index.sources.first
|
|
290
|
+
|
|
291
|
+
field = source.sql_joined_field.detect { |field| field[/tags/] }
|
|
292
|
+
declaration, query = field.split(/;\s+/)
|
|
293
|
+
|
|
294
|
+
declaration.should == 'tags from query'
|
|
295
|
+
query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. ORDER BY .articles.\..user_id. ASC\s?$/)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it "generates a SQL query with joins when appropriate for MVFs" do
|
|
299
|
+
index.definition_block = Proc.new {
|
|
300
|
+
indexes taggings.tag.name, :as => :tags, :source => :ranged_query
|
|
301
|
+
}
|
|
302
|
+
index.render
|
|
303
|
+
|
|
304
|
+
field = source.sql_joined_field.detect { |field| field[/tags/] }
|
|
305
|
+
declaration, query, range = field.split(/;\s+/)
|
|
306
|
+
|
|
307
|
+
declaration.should == 'tags from ranged-query'
|
|
308
|
+
query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. >= \$start\) AND \(.taggings.\..article_id. <= \$end\) ORDER BY .taggings.\..article_id. ASC$/)
|
|
309
|
+
range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
it "respects custom SQL snippets as the query value" do
|
|
313
|
+
index.definition_block = Proc.new {
|
|
314
|
+
indexes 'My Custom SQL Query', :as => :tags, :source => :query
|
|
315
|
+
}
|
|
316
|
+
index.render
|
|
317
|
+
|
|
318
|
+
field = source.sql_joined_field.detect { |field| field[/tags/] }
|
|
319
|
+
declaration, query = field.split(/;\s+/)
|
|
320
|
+
|
|
321
|
+
declaration.should == 'tags from query'
|
|
322
|
+
query.should == 'My Custom SQL Query'
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
it "respects custom SQL snippets as the ranged query value" do
|
|
326
|
+
index.definition_block = Proc.new {
|
|
327
|
+
indexes 'My Custom SQL Query; And a Range', :as => :tags,
|
|
328
|
+
:source => :ranged_query
|
|
329
|
+
}
|
|
330
|
+
index.render
|
|
331
|
+
|
|
332
|
+
field = source.sql_joined_field.detect { |field| field[/tags/] }
|
|
333
|
+
declaration, query, range = field.split(/;\s+/)
|
|
334
|
+
|
|
335
|
+
declaration.should == 'tags from ranged-query'
|
|
336
|
+
query.should == 'My Custom SQL Query'
|
|
337
|
+
range.should == 'And a Range'
|
|
338
|
+
end
|
|
62
339
|
end
|
|
@@ -8,12 +8,13 @@ class SphinxController
|
|
|
8
8
|
config.render_to_file && index
|
|
9
9
|
|
|
10
10
|
ThinkingSphinx::Configuration.reset
|
|
11
|
-
ActiveSupport::Dependencies.clear
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
ActiveSupport::Dependencies.loaded.each do |path|
|
|
13
|
+
$LOADED_FEATURES.delete "#{path}.rb"
|
|
15
14
|
end
|
|
16
15
|
|
|
16
|
+
ActiveSupport::Dependencies.clear
|
|
17
|
+
|
|
17
18
|
config.searchd.mysql41 = 9307
|
|
18
19
|
config.settings['quiet_deltas'] = true
|
|
19
20
|
config.settings['attribute_updates'] = true
|
data/spec/internal/db/schema.rb
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
ActiveRecord::Schema.define do
|
|
2
|
+
create_table(:admin_people, :force => true) do |t|
|
|
3
|
+
t.string :name
|
|
4
|
+
t.timestamps
|
|
5
|
+
end
|
|
6
|
+
|
|
2
7
|
create_table(:animals, :force => true) do |t|
|
|
3
8
|
t.string :name
|
|
4
9
|
t.string :type
|
|
@@ -21,6 +26,11 @@ ActiveRecord::Schema.define do
|
|
|
21
26
|
t.timestamps
|
|
22
27
|
end
|
|
23
28
|
|
|
29
|
+
create_table(:books_genres, :force => true, :id => false) do |t|
|
|
30
|
+
t.integer :book_id
|
|
31
|
+
t.integer :genre_id
|
|
32
|
+
end
|
|
33
|
+
|
|
24
34
|
create_table(:cities, :force => true) do |t|
|
|
25
35
|
t.string :name
|
|
26
36
|
t.float :lat
|
|
@@ -32,6 +42,10 @@ ActiveRecord::Schema.define do
|
|
|
32
42
|
t.timestamps
|
|
33
43
|
end
|
|
34
44
|
|
|
45
|
+
create_table(:genres, :force => true) do |t|
|
|
46
|
+
t.string :name
|
|
47
|
+
end
|
|
48
|
+
|
|
35
49
|
create_table(:products, :force => true) do |t|
|
|
36
50
|
t.string :name
|
|
37
51
|
end
|