dbldots_oedipus 0.0.16
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/.gitignore +10 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +435 -0
- data/Rakefile +26 -0
- data/ext/oedipus/extconf.rb +72 -0
- data/ext/oedipus/lexing.c +96 -0
- data/ext/oedipus/lexing.h +20 -0
- data/ext/oedipus/oedipus.c +339 -0
- data/ext/oedipus/oedipus.h +58 -0
- data/lib/oedipus.rb +40 -0
- data/lib/oedipus/comparison.rb +88 -0
- data/lib/oedipus/comparison/between.rb +21 -0
- data/lib/oedipus/comparison/equal.rb +21 -0
- data/lib/oedipus/comparison/gt.rb +21 -0
- data/lib/oedipus/comparison/gte.rb +21 -0
- data/lib/oedipus/comparison/in.rb +21 -0
- data/lib/oedipus/comparison/lt.rb +21 -0
- data/lib/oedipus/comparison/lte.rb +21 -0
- data/lib/oedipus/comparison/not.rb +25 -0
- data/lib/oedipus/comparison/not_equal.rb +21 -0
- data/lib/oedipus/comparison/not_in.rb +21 -0
- data/lib/oedipus/comparison/outside.rb +21 -0
- data/lib/oedipus/comparison/shortcuts.rb +144 -0
- data/lib/oedipus/connection.rb +124 -0
- data/lib/oedipus/connection/pool.rb +133 -0
- data/lib/oedipus/connection/registry.rb +56 -0
- data/lib/oedipus/connection_error.rb +14 -0
- data/lib/oedipus/index.rb +320 -0
- data/lib/oedipus/query_builder.rb +185 -0
- data/lib/oedipus/rspec/test_rig.rb +132 -0
- data/lib/oedipus/version.rb +12 -0
- data/oedipus.gemspec +42 -0
- data/spec/data/.gitkeep +0 -0
- data/spec/integration/connection/registry_spec.rb +50 -0
- data/spec/integration/connection_spec.rb +156 -0
- data/spec/integration/index_spec.rb +442 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/comparison/between_spec.rb +36 -0
- data/spec/unit/comparison/equal_spec.rb +22 -0
- data/spec/unit/comparison/gt_spec.rb +22 -0
- data/spec/unit/comparison/gte_spec.rb +22 -0
- data/spec/unit/comparison/in_spec.rb +22 -0
- data/spec/unit/comparison/lt_spec.rb +22 -0
- data/spec/unit/comparison/lte_spec.rb +22 -0
- data/spec/unit/comparison/not_equal_spec.rb +22 -0
- data/spec/unit/comparison/not_in_spec.rb +22 -0
- data/spec/unit/comparison/not_spec.rb +37 -0
- data/spec/unit/comparison/outside_spec.rb +36 -0
- data/spec/unit/comparison/shortcuts_spec.rb +125 -0
- data/spec/unit/comparison_spec.rb +109 -0
- data/spec/unit/query_builder_spec.rb +205 -0
- metadata +164 -0
@@ -0,0 +1,442 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
##
|
4
|
+
# Oedipus Sphinx 2 Search.
|
5
|
+
# Copyright © 2012 Chris Corbyn.
|
6
|
+
#
|
7
|
+
# See LICENSE file for details.
|
8
|
+
##
|
9
|
+
|
10
|
+
require "spec_helper"
|
11
|
+
require "oedipus/rspec/test_rig"
|
12
|
+
|
13
|
+
describe Oedipus::Index do
|
14
|
+
include_context "oedipus test rig"
|
15
|
+
include_context "oedipus posts_rt"
|
16
|
+
|
17
|
+
let(:conn) { connection }
|
18
|
+
let(:index) { Oedipus::Index.new(:posts_rt, conn) }
|
19
|
+
|
20
|
+
describe "#insert" do
|
21
|
+
context "with valid data" do
|
22
|
+
it "returns the number of rows inserted" do
|
23
|
+
index.insert(
|
24
|
+
10,
|
25
|
+
title: "Badgers",
|
26
|
+
body: "They live in setts, do badgers.",
|
27
|
+
views: 721,
|
28
|
+
user_id: 7
|
29
|
+
).should == 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "with invalid data" do
|
34
|
+
it "raises an error" do
|
35
|
+
expect {
|
36
|
+
index.insert(
|
37
|
+
10,
|
38
|
+
bad_field: "Invalid",
|
39
|
+
body: "They live in setts, do badgers.",
|
40
|
+
views: 721,
|
41
|
+
user_id: 7
|
42
|
+
)
|
43
|
+
}.to raise_error(Oedipus::ConnectionError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#fetch" do
|
49
|
+
before(:each) do
|
50
|
+
index.insert(1, title: "Badgers and foxes", views: 150)
|
51
|
+
index.insert(2, title: "Rabbits and hares", views: 73)
|
52
|
+
index.insert(3, title: "Clowns and cannon girls", views: 1)
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with a valid document ID" do
|
56
|
+
it "returns the matched document" do
|
57
|
+
index.fetch(2).should == { id: 2, views: 73, user_id: 0, state: "" }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with a bad document ID" do
|
62
|
+
it "returns nil" do
|
63
|
+
index.fetch(7).should be_nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#update" do
|
69
|
+
before(:each) do
|
70
|
+
index.insert(1, title: "Badgers and foxes", views: 150, user_id: 7)
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with valid data" do
|
74
|
+
it "returns the number of rows modified" do
|
75
|
+
index.update(1, views: 721).should == 1
|
76
|
+
end
|
77
|
+
|
78
|
+
it "modifies the data" do
|
79
|
+
index.update(1, views: 721)
|
80
|
+
index.fetch(1).should == { id: 1, views: 721, user_id: 7, state: "" }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "with unmatched data" do
|
85
|
+
it "returns 0" do
|
86
|
+
index.update(3, views: 721).should == 0
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "with invalid data" do
|
91
|
+
it "raises an error" do
|
92
|
+
expect {
|
93
|
+
index.update(1, bad_field: "Invalid", views: 721)
|
94
|
+
}.to raise_error(Oedipus::ConnectionError)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#replace" do
|
100
|
+
before(:each) do
|
101
|
+
index.insert(
|
102
|
+
1,
|
103
|
+
title: "Badgers",
|
104
|
+
body: "They live in setts, do badgers.",
|
105
|
+
views: 721,
|
106
|
+
user_id: 7
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
context "with valid existing data" do
|
111
|
+
it "returns the number of rows inserted" do
|
112
|
+
index.replace(1, title: "Badgers and foxes", views: 150).should == 1
|
113
|
+
end
|
114
|
+
|
115
|
+
it "entirely replaces the record" do
|
116
|
+
index.replace(1, title: "Badgers and foxes", views: 150)
|
117
|
+
index.fetch(1).should == { id: 1, views: 150, user_id: 0, state: "" }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "with valid new data" do
|
122
|
+
it "returns the number of rows inserted" do
|
123
|
+
index.replace(2, title: "Beer and wine", views: 15).should == 1
|
124
|
+
end
|
125
|
+
|
126
|
+
it "entirely replaces the record" do
|
127
|
+
index.replace(2, title: "Beer and wine", views: 15)
|
128
|
+
index.fetch(2).should == { id: 2, views: 15, user_id: 0, state: "" }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "with invalid data" do
|
133
|
+
it "raises an error" do
|
134
|
+
expect {
|
135
|
+
index.replace(1, bad_field: "Badgers and foxes", views: 150)
|
136
|
+
}.to raise_error(Oedipus::ConnectionError)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#delete" do
|
142
|
+
before(:each) do
|
143
|
+
index.insert(
|
144
|
+
1,
|
145
|
+
title: "Badgers",
|
146
|
+
body: "They live in setts, do badgers.",
|
147
|
+
views: 721,
|
148
|
+
user_id: 7
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
context "with valid existing data" do
|
153
|
+
it "entirely deletes the record" do
|
154
|
+
index.delete(1)
|
155
|
+
index.fetch(1).should be_nil
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "#search" do
|
161
|
+
before(:each) do
|
162
|
+
index.insert(1, title: "Badgers and foxes", views: 150)
|
163
|
+
index.insert(2, title: "Rabbits and hares", views: 87)
|
164
|
+
index.insert(3, title: "Badgers in the wild", views: 41)
|
165
|
+
index.insert(4, title: "Badgers for all, badgers!", views: 3003)
|
166
|
+
end
|
167
|
+
|
168
|
+
context "by fulltext matching" do
|
169
|
+
it "indicates the number of records found" do
|
170
|
+
index.search("badgers")[:total_found].should == 3
|
171
|
+
end
|
172
|
+
|
173
|
+
it "includes the matches records" do
|
174
|
+
index.search("badgers")[:records].should == [
|
175
|
+
{ id: 1, views: 150, user_id: 0, state: "" },
|
176
|
+
{ id: 3, views: 41, user_id: 0, state: "" },
|
177
|
+
{ id: 4, views: 3003, user_id: 0, state: "" }
|
178
|
+
]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "by attribute filtering" do
|
183
|
+
it "indicates the number of records found" do
|
184
|
+
index.search(views: 40..90)[:total_found].should == 2
|
185
|
+
end
|
186
|
+
|
187
|
+
it "includes the matches records" do
|
188
|
+
index.search(views: 40..90)[:records].should == [
|
189
|
+
{ id: 2, views: 87, user_id: 0, state: "" },
|
190
|
+
{ id: 3, views: 41, user_id: 0, state: "" }
|
191
|
+
]
|
192
|
+
end
|
193
|
+
|
194
|
+
pending "the sphinxql grammar does not currently support this, though I'm patching it" do
|
195
|
+
context "with string attributes" do
|
196
|
+
before(:each) do
|
197
|
+
index.insert(5, title: "No more badgers, please", views: 0, state: "new")
|
198
|
+
end
|
199
|
+
|
200
|
+
it "filters by the string attribute" do
|
201
|
+
index.search(state: "new")[:records].should == [{ id: 5, views: 0, user_id: 0, state: "new" }]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
context "by fulltext with attribute filtering" do
|
208
|
+
it "indicates the number of records found" do
|
209
|
+
index.search("badgers", views: Oedipus.gt(100))[:total_found].should == 2
|
210
|
+
end
|
211
|
+
|
212
|
+
it "includes the matches records" do
|
213
|
+
index.search("badgers", views: Oedipus.gt(100))[:records].should == [
|
214
|
+
{ id: 1, views: 150, user_id: 0, state: "" },
|
215
|
+
{ id: 4, views: 3003, user_id: 0, state: "" }
|
216
|
+
]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
context "with limits" do
|
221
|
+
it "still indicates the number of records found" do
|
222
|
+
index.search("badgers", limit: 2)[:total_found].should == 3
|
223
|
+
end
|
224
|
+
|
225
|
+
it "returns the limited subset of the results" do
|
226
|
+
index.search("badgers", limit: 2)[:records].should == [
|
227
|
+
{ id: 1, views: 150, user_id: 0, state: "" },
|
228
|
+
{ id: 3, views: 41, user_id: 0, state: "" }
|
229
|
+
]
|
230
|
+
end
|
231
|
+
|
232
|
+
it "can use an offset" do
|
233
|
+
index.search("badgers", limit: 1, offset: 1)[:records].should == [
|
234
|
+
{ id: 3, views: 41, user_id: 0, state: "" }
|
235
|
+
]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context "with ordering" do
|
240
|
+
it "returns the results ordered accordingly" do
|
241
|
+
index.search("badgers", order: {views: :desc})[:records].should == [
|
242
|
+
{ id: 4, views: 3003, user_id: 0, state: "" },
|
243
|
+
{ id: 1, views: 150, user_id: 0, state: "" },
|
244
|
+
{ id: 3, views: 41, user_id: 0, state: "" },
|
245
|
+
]
|
246
|
+
end
|
247
|
+
|
248
|
+
context "by relevance" do
|
249
|
+
it "returns the results ordered by most relevant" do
|
250
|
+
records = index.search("badgers", order: {relevance: :desc})[:records]
|
251
|
+
records.first[:relevance].should > records.last[:relevance]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
context "with attribute additions" do
|
257
|
+
it "fetches the additional attributes" do
|
258
|
+
index.search("badgers", attrs: [:*, "7 AS x"])[:records].should == [
|
259
|
+
{ id: 1, views: 150, user_id: 0, state: "", x: 7 },
|
260
|
+
{ id: 3, views: 41, user_id: 0, state: "", x: 7 },
|
261
|
+
{ id: 4, views: 3003, user_id: 0, state: "", x: 7 },
|
262
|
+
]
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context "with attribute restrictions" do
|
267
|
+
it "fetches the restricted attributes" do
|
268
|
+
index.search("badgers", attrs: [:id, :views])[:records].should == [
|
269
|
+
{ id: 1, views: 150 },
|
270
|
+
{ id: 3, views: 41 },
|
271
|
+
{ id: 4, views: 3003 },
|
272
|
+
]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
describe "#search", "with :facets" do
|
278
|
+
before(:each) do
|
279
|
+
index.insert(1, title: "Badgers and foxes", body: "Badgers", views: 150, user_id: 1)
|
280
|
+
index.insert(2, title: "Rabbits and hares", body: "Rabbits", views: 87, user_id: 1)
|
281
|
+
index.insert(3, title: "Badgers in the wild", body: "Test", views: 41, user_id: 2)
|
282
|
+
index.insert(4, title: "Badgers for all!", body: "For all", views: 3003, user_id: 1)
|
283
|
+
end
|
284
|
+
|
285
|
+
context "with additional attribute filters" do
|
286
|
+
let(:results) do
|
287
|
+
index.search(
|
288
|
+
"badgers",
|
289
|
+
facets: {
|
290
|
+
popular: {views: Oedipus.gte(50)},
|
291
|
+
di_carla: {user_id: 2}
|
292
|
+
}
|
293
|
+
)
|
294
|
+
end
|
295
|
+
|
296
|
+
it "returns the main results in the top-level" do
|
297
|
+
results[:records].should == [
|
298
|
+
{ id: 1, views: 150, user_id: 1, state: "" },
|
299
|
+
{ id: 3, views: 41, user_id: 2, state: "" },
|
300
|
+
{ id: 4, views: 3003, user_id: 1, state: "" }
|
301
|
+
]
|
302
|
+
end
|
303
|
+
|
304
|
+
it "applies the filters on top of the base query" do
|
305
|
+
results[:facets][:popular][:records].should == [
|
306
|
+
{ id: 1, views: 150, user_id: 1, state: "" },
|
307
|
+
{ id: 4, views: 3003, user_id: 1, state: "" }
|
308
|
+
]
|
309
|
+
results[:facets][:di_carla][:records].should == [
|
310
|
+
{ id: 3, views: 41, user_id: 2, state: "" }
|
311
|
+
]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
context "with overriding attribute filters" do
|
316
|
+
let(:results) do
|
317
|
+
index.search(
|
318
|
+
"badgers",
|
319
|
+
user_id: 1,
|
320
|
+
facets: {
|
321
|
+
di_carla: {user_id: 2}
|
322
|
+
}
|
323
|
+
)
|
324
|
+
end
|
325
|
+
|
326
|
+
it "applies the filters on top of the base query" do
|
327
|
+
results[:facets][:di_carla][:records].should == [
|
328
|
+
{ id: 3, views: 41, user_id: 2, state: "" }
|
329
|
+
]
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context "with overriding overriding fulltext queries" do
|
334
|
+
let(:results) do # FIXME: Weird RSpec bug is not clearing the previous result, hence the ridiculous naming
|
335
|
+
index.search(
|
336
|
+
"badgers",
|
337
|
+
facets: {
|
338
|
+
rabbits: "rabbits"
|
339
|
+
}
|
340
|
+
)
|
341
|
+
end
|
342
|
+
|
343
|
+
it "entirely replaces the base query" do
|
344
|
+
results[:facets][:rabbits][:records].should == [
|
345
|
+
{ id: 2, views: 87, user_id: 1, state: "" }
|
346
|
+
]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
context "with overriding refined fulltext queries" do
|
351
|
+
let(:results) do
|
352
|
+
index.search(
|
353
|
+
"badgers",
|
354
|
+
facets: {
|
355
|
+
in_body: "@body (%{query})"
|
356
|
+
}
|
357
|
+
)
|
358
|
+
end
|
359
|
+
|
360
|
+
it "merges the queries" do
|
361
|
+
results[:facets][:in_body][:records].should == [
|
362
|
+
{ id: 1, views: 150, user_id: 1, state: "" },
|
363
|
+
]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
context "with multi-dimensional facets" do
|
368
|
+
let(:results) do
|
369
|
+
index.search(
|
370
|
+
"badgers",
|
371
|
+
facets: {
|
372
|
+
popular: {
|
373
|
+
views: Oedipus.gte(50),
|
374
|
+
facets: {
|
375
|
+
with_foxes: "%{query} & foxes"
|
376
|
+
}
|
377
|
+
},
|
378
|
+
}
|
379
|
+
)
|
380
|
+
end
|
381
|
+
|
382
|
+
it "merges the results in the outer facets" do
|
383
|
+
results[:facets][:popular][:records].should == [
|
384
|
+
{ id: 1, views: 150, user_id: 1, state: "" },
|
385
|
+
{ id: 4, views: 3003, user_id: 1, state: "" }
|
386
|
+
]
|
387
|
+
end
|
388
|
+
|
389
|
+
it "merges the results in the inner facets" do
|
390
|
+
results[:facets][:popular][:facets][:with_foxes][:records].should == [
|
391
|
+
{ id: 1, views: 150, user_id: 1, state: "" }
|
392
|
+
]
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
describe "#multi_search" do
|
398
|
+
before(:each) do
|
399
|
+
index.insert(1, title: "Badgers and foxes", views: 150, user_id: 1)
|
400
|
+
index.insert(2, title: "Rabbits and hares", views: 87, user_id: 1)
|
401
|
+
index.insert(3, title: "Badgers in the wild", views: 41, user_id: 2)
|
402
|
+
index.insert(4, title: "Badgers for all!", views: 3003, user_id: 1)
|
403
|
+
end
|
404
|
+
|
405
|
+
context "by fulltext querying" do
|
406
|
+
it "indicates the number of results for each query" do
|
407
|
+
results = index.multi_search(
|
408
|
+
badgers: "badgers",
|
409
|
+
rabbits: "rabbits"
|
410
|
+
)
|
411
|
+
results[:badgers][:total_found].should == 3
|
412
|
+
results[:rabbits][:total_found].should == 1
|
413
|
+
end
|
414
|
+
|
415
|
+
it "returns the records for each search" do
|
416
|
+
results = index.multi_search(
|
417
|
+
badgers: "badgers",
|
418
|
+
rabbits: "rabbits"
|
419
|
+
)
|
420
|
+
results[:badgers][:records].should == [
|
421
|
+
{ id: 1, views: 150, user_id: 1, state: "" },
|
422
|
+
{ id: 3, views: 41, user_id: 2, state: "" },
|
423
|
+
{ id: 4, views: 3003, user_id: 1, state: "" }
|
424
|
+
]
|
425
|
+
results[:rabbits][:records].should == [
|
426
|
+
{ id: 2, views: 87, user_id: 1, state: "" }
|
427
|
+
]
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
context "by attribute filtering" do
|
432
|
+
it "indicates the number of results for each query" do
|
433
|
+
results = index.multi_search(
|
434
|
+
shiela: {user_id: 1},
|
435
|
+
barry: {user_id: 2}
|
436
|
+
)
|
437
|
+
results[:shiela][:total_found].should == 3
|
438
|
+
results[:barry][:total_found].should == 1
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
data/spec/spec_helper.rb
ADDED