actsasflinn-ruby-tokyotyrant 0.1.9 → 0.2.0
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.rdoc +22 -4
- data/Rakefile +1 -1
- data/ext/tokyo_tyrant_query.c +9 -0
- data/ext/tokyo_tyrant_table.c +43 -0
- data/spec/tokyo_tyrant_query_spec.rb +10 -0
- data/spec/tokyo_tyrant_table_spec.rb +20 -0
- metadata +1 -1
data/README.rdoc
CHANGED
@@ -4,7 +4,7 @@ This is a c extension for Ruby to access TokyoTyrant databases. It currently su
|
|
4
4
|
|
5
5
|
== Install
|
6
6
|
|
7
|
-
# install tokyocabinet (1.4.
|
7
|
+
# install tokyocabinet (1.4.30) and tokyotyrant (requires 1.1.33)
|
8
8
|
# after installing tc and tt on linux I had to /sbin/ldconfig (as root)
|
9
9
|
gem sources -a http://gems.github.com
|
10
10
|
sudo gem install actsasflinn-ruby-tokyotyrant
|
@@ -135,15 +135,32 @@ This is not in production but the initial benchmarks are very interesting. Resul
|
|
135
135
|
|
136
136
|
# full-text search with all tokens in
|
137
137
|
t.query{ |q| q.condition(:paragraph, :ftsand, 'logos word') }
|
138
|
-
# => ["chapter:1:paragraph:12", "chapter:1:paragraph:14", "chapter:1:paragraph:17", "chapter:1:paragraph:19", "chapter:1:paragraph:24", "chapter:1:paragraph:27", "chapter:1:paragraph:43", "chapter:1:paragraph:53", "
|
138
|
+
# => ["chapter:1:paragraph:12", "chapter:1:paragraph:14", "chapter:1:paragraph:17", "chapter:1:paragraph:19", "chapter:1:paragraph:24", "chapter:1:paragraph:27", "chapter:1:paragraph:43", "chapter:1:paragraph:53", "... lots more ..."]
|
139
139
|
|
140
140
|
# full-text search with at least one token in
|
141
141
|
t.query{ |q| q.condition(:paragraph, :ftsor, 'sermon key') }
|
142
|
-
# => ["chapter:5:paragraph:1", "chapter:9:paragraph:3", "chapter:10:paragraph:1", "chapter:10:paragraph:4", "chapter:10:paragraph:28", "chapter:11:paragraph:3", "chapter:11:paragraph:66", "chapter:11:paragraph:69", "
|
142
|
+
# => ["chapter:5:paragraph:1", "chapter:9:paragraph:3", "chapter:10:paragraph:1", "chapter:10:paragraph:4", "chapter:10:paragraph:28", "chapter:11:paragraph:3", "chapter:11:paragraph:66", "chapter:11:paragraph:69", "... lots more ..."]
|
143
143
|
|
144
144
|
# negated full-text search with at least one token in
|
145
145
|
t.query{ |q| q.condition(:paragraph, '!ftsor', 'the god he and I that said') }
|
146
|
-
# => ["chapter:1:paragraph:95", "chapter:1:paragraph:96", "chapter:1:paragraph:97", "chapter:1:paragraph:98", "chapter:1:paragraph:99", "chapter:2:paragraph:3", "chapter:2:paragraph:5", "chapter:2:paragraph:6", "
|
146
|
+
# => ["chapter:1:paragraph:95", "chapter:1:paragraph:96", "chapter:1:paragraph:97", "chapter:1:paragraph:98", "chapter:1:paragraph:99", "chapter:2:paragraph:3", "chapter:2:paragraph:5", "chapter:2:paragraph:6", "... lots more ..."]
|
147
|
+
|
148
|
+
==== Meta Search (Multi Query)
|
149
|
+
|
150
|
+
query1 = t.prepare_query{ |q| q.condition(:paragraph, :fts, 'rebirth') }
|
151
|
+
query2 = t.prepare_query{ |q| q.condition(:paragraph, :fts, 'logos') }
|
152
|
+
|
153
|
+
# Get the union of two query sets (OR)
|
154
|
+
t.search(:union, query1, query2)
|
155
|
+
# => ["chapter:13:paragraph:4", "chapter:13:paragraph:5", "chapter:13:paragraph:7", "chapter:13:paragraph:19", "chapter:13:paragraph:27", "chapter:13:paragraph:44", "chapter:13:paragraph:57", "... lots more ..."]
|
156
|
+
|
157
|
+
# Get the intersection of two query sets (AND)
|
158
|
+
t.search(:intersection, query1, query2)
|
159
|
+
# => ["chapter:13:paragraph:5", "chapter:13:paragraph:44", "chapter:13:paragraph:69"]
|
160
|
+
|
161
|
+
# Get the difference of two query sets (ANDNOT)
|
162
|
+
t.search(:diff, query1, query2)
|
163
|
+
# => ["chapter:13:paragraph:4", "chapter:13:paragraph:7", "chapter:13:paragraph:19", "chapter:13:paragraph:27", "chapter:13:paragraph:57", "chapter:13:paragraph:125"]
|
147
164
|
|
148
165
|
=== Lua Extension
|
149
166
|
|
@@ -159,3 +176,4 @@ This is not in production but the initial benchmarks are very interesting. Resul
|
|
159
176
|
* Flinn Mueller (actsasflinn) author/maintainer
|
160
177
|
* Justin Reagor (cheapRoc) specs
|
161
178
|
* Seth Yates (sethyates) run method (lua ext)
|
179
|
+
* John Mettraux (jmettraux) inspiration (rufus-tokyo)
|
data/Rakefile
CHANGED
@@ -20,7 +20,7 @@ CLEAN.include('pkg', 'tmp')
|
|
20
20
|
|
21
21
|
gemspec = Gem::Specification.new do |s|
|
22
22
|
s.name = 'ruby-tokyotyrant'
|
23
|
-
s.version = '0.
|
23
|
+
s.version = '0.2.0'
|
24
24
|
s.authors = [ 'Flinn' ]
|
25
25
|
s.email = 'flinn@actsasflinn.com'
|
26
26
|
s.homepage = 'http://github.com/actsasflinn/ruby-tokyotyrant/'
|
data/ext/tokyo_tyrant_query.c
CHANGED
@@ -143,6 +143,14 @@ static VALUE cQuery_set_pkey(VALUE vself, VALUE vpkey) {
|
|
143
143
|
return rb_iv_set(vself, "@pkey", vpkey);
|
144
144
|
}
|
145
145
|
|
146
|
+
static VALUE cQuery_hint(VALUE vself){
|
147
|
+
VALUE vqry;
|
148
|
+
RDBQRY *qry;
|
149
|
+
vqry = rb_iv_get(vself, RDBQRYVNDATA);
|
150
|
+
Data_Get_Struct(vqry, RDBQRY, qry);
|
151
|
+
return rb_str_new2(tcrdbqryhint(qry));
|
152
|
+
}
|
153
|
+
|
146
154
|
void init_query(){
|
147
155
|
rb_define_const(cQuery, "CSTREQ", INT2NUM(RDBQCSTREQ));
|
148
156
|
rb_define_const(cQuery, "CSTRINC", INT2NUM(RDBQCSTRINC));
|
@@ -185,4 +193,5 @@ void init_query(){
|
|
185
193
|
rb_define_method(cQuery, "get", cQuery_get, 0);
|
186
194
|
rb_define_method(cQuery, "set_pkey", cQuery_set_pkey, 1);
|
187
195
|
rb_define_alias(cQuery, "pkey=", "set_pkey");
|
196
|
+
rb_define_method(cQuery, "hint", cQuery_hint, 0);
|
188
197
|
}
|
data/ext/tokyo_tyrant_table.c
CHANGED
@@ -267,6 +267,48 @@ static VALUE cTable_find(VALUE vself){
|
|
267
267
|
return vary;
|
268
268
|
}
|
269
269
|
|
270
|
+
static VALUE cTable_search(int argc, VALUE *argv, VALUE vself){
|
271
|
+
VALUE vqrys, vkeys, vtype, vhash, vcols;
|
272
|
+
TCMAP *recs, *cols;
|
273
|
+
int qsiz, type, j;
|
274
|
+
const char *kbuf;
|
275
|
+
int ksiz, vsiz;
|
276
|
+
TCRDB *db = mTokyoTyrant_getdb(vself);
|
277
|
+
|
278
|
+
// rb_scan_args(argc, argv, "*", &vargs);
|
279
|
+
rb_scan_args(argc, argv, "1*", &vtype, &vqrys);
|
280
|
+
|
281
|
+
// vtype = rb_ary_pop(vargs);
|
282
|
+
qsiz = argc - 1;
|
283
|
+
RDBQRY *qrys[qsiz];
|
284
|
+
|
285
|
+
vtype = StringValueEx(vtype);
|
286
|
+
type = tctdbstrtometasearcytype(RSTRING_PTR(vtype));
|
287
|
+
|
288
|
+
for(j = 0; j < qsiz; j++){
|
289
|
+
VALUE vqry = rb_iv_get(rb_ary_entry(vqrys, j), RDBQRYVNDATA);
|
290
|
+
Data_Get_Struct(vqry, RDBQRY, qrys[j]);
|
291
|
+
}
|
292
|
+
TCLIST *res = tcrdbmetasearch(qrys, qsiz, type);
|
293
|
+
vkeys = listtovary(res);
|
294
|
+
tclistdel(res);
|
295
|
+
|
296
|
+
recs = varytomap(vkeys);
|
297
|
+
if(!tcrdbget3(db, recs)) return Qnil;
|
298
|
+
vhash = rb_hash_new();
|
299
|
+
tcmapiterinit(recs);
|
300
|
+
while((kbuf = tcmapiternext(recs, &ksiz)) != NULL){
|
301
|
+
const char *vbuf = tcmapiterval(kbuf, &vsiz);
|
302
|
+
cols = tcstrsplit4(vbuf, vsiz);
|
303
|
+
vcols = maptovhash(cols);
|
304
|
+
tcmapdel(cols);
|
305
|
+
rb_hash_aset(vhash, StringRaw(kbuf, ksiz), vcols);
|
306
|
+
}
|
307
|
+
tcmapdel(recs);
|
308
|
+
|
309
|
+
return vkeys;
|
310
|
+
}
|
311
|
+
|
270
312
|
void init_table(){
|
271
313
|
rb_define_method(cTable, "mput", cTable_mput, 1);
|
272
314
|
rb_define_alias(cTable, "lput", "mput"); // Rufus Compat
|
@@ -291,4 +333,5 @@ void init_table(){
|
|
291
333
|
rb_define_method(cTable, "prepare_query", cTable_prepare_query, 0);
|
292
334
|
rb_define_method(cTable, "query", cTable_query, 0);
|
293
335
|
rb_define_method(cTable, "find", cTable_find, 0);
|
336
|
+
rb_define_method(cTable, "search", cTable_search, -1);
|
294
337
|
}
|
@@ -120,4 +120,14 @@ describe TokyoTyrant::Query, "with an open database" do
|
|
120
120
|
{'type'=>"Cucumber", 'code'=>"4595", :pk=>"4595", 'variety'=>"Lemon"},
|
121
121
|
{'type'=>"Cucumber", 'code'=>"4596", :pk=>"4596", 'variety'=>"Pickling / Gherkin"}]
|
122
122
|
end
|
123
|
+
|
124
|
+
it "should return a query hint" do
|
125
|
+
@db.set_index(:type, :lexical)
|
126
|
+
q = @db.query
|
127
|
+
q.condition(:type, :streq, 'Cucumber')
|
128
|
+
q.order_by(:code)
|
129
|
+
q.limit(3)
|
130
|
+
q.run
|
131
|
+
q.hint.should == "\nusing an index: \"type\" asc (STREQ)\nresult set size: 5\nsorting the result set: \"code\"\n"
|
132
|
+
end
|
123
133
|
end
|
@@ -267,4 +267,24 @@ describe TokyoTyrant::Table, "with an open database" do
|
|
267
267
|
it "should run lua extensions" do
|
268
268
|
@db.ext('echo', 'hello', 'world').should == "hello\tworld"
|
269
269
|
end
|
270
|
+
|
271
|
+
it "should perform meta search with multiple query objects" do
|
272
|
+
keys = %w[falafel humus couscous tabbouleh tzatziki peanutbutter]
|
273
|
+
values = [{ 'ingredient' => 'chickpeas' },
|
274
|
+
{ 'ingredient' => 'chickpeas' },
|
275
|
+
{ 'ingredient' => 'semolina' },
|
276
|
+
{ 'ingredient' => 'bulgar' },
|
277
|
+
{ 'ingredient' => 'yogurt' },
|
278
|
+
{ 'ingredient' => 'peanuts'}]
|
279
|
+
|
280
|
+
keys.each_with_index{ |k,i| @db[k] = values[i] }
|
281
|
+
|
282
|
+
query1 = @db.prepare_query{ |q| q.condition('ingredient', :streq, 'yogurt').order_by('ingredient') }
|
283
|
+
query2 = @db.prepare_query{ |q| q.condition('ingredient', :streq, 'chickpeas').order_by('ingredient') }
|
284
|
+
query3 = @db.prepare_query{ |q| q.condition('ingredient', :ftsex, 'pea').order_by('ingredient') }
|
285
|
+
|
286
|
+
@db.search(:union, query1, query2).should == ["falafel", "humus", "tzatziki"]
|
287
|
+
@db.search(:diff, query1, query2).should == ["tzatziki"]
|
288
|
+
@db.search(:intersection, query2, query3).should == ["falafel", "humus"]
|
289
|
+
end
|
270
290
|
end
|