oklahoma_mixer 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +10 -0
- data/TODO.rdoc +4 -4
- data/lib/oklahoma_mixer.rb +1 -1
- data/lib/oklahoma_mixer/array_list.rb +4 -9
- data/lib/oklahoma_mixer/array_list/c.rb +7 -3
- data/lib/oklahoma_mixer/hash_database.rb +7 -3
- data/lib/oklahoma_mixer/table_database.rb +78 -73
- data/lib/oklahoma_mixer/table_database/c.rb +3 -0
- data/test/shared/tuning_tests.rb +9 -0
- data/test/table_database/query_test.rb +348 -0
- data/test/table_database/read_only_query_test.rb +155 -0
- metadata +27 -13
data/CHANGELOG.rdoc
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
|
3
3
|
Below is a complete listing of changes for each revision of Oklahoma Mixer.
|
4
4
|
|
5
|
+
== 0.4.0
|
6
|
+
|
7
|
+
* Added the read_only?() method
|
8
|
+
* Added error handling for Table Databases queries
|
9
|
+
* Added support for Table Databases queries on read only databases
|
10
|
+
* Added support for iteration blocks to Table Databases searches
|
11
|
+
* Modified Table Databases blocks to yield tuples consistent with the iterators
|
12
|
+
* Improved count() performance by removing unneeded document conversion
|
13
|
+
* Improved paginate() performance by avoiding double type conversion
|
14
|
+
|
5
15
|
== 0.3.0
|
6
16
|
|
7
17
|
* Added the empty?() method
|
data/TODO.rdoc
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
The following is a list of planned expansions for Oklahoma Mixer in the order I
|
4
4
|
intend to address them.
|
5
5
|
|
6
|
-
1.
|
7
|
-
2.
|
8
|
-
3.
|
9
|
-
4.
|
6
|
+
1. Include some higher level abstractions like mixed tables, queues, and shards
|
7
|
+
2. Add support for Tokyo Tyrant
|
8
|
+
3. Ensure Ruby 1.9 compatibility
|
9
|
+
4. Write API documentation
|
10
10
|
5. Add support for Tokyo Dystopia
|
data/lib/oklahoma_mixer.rb
CHANGED
@@ -11,17 +11,12 @@ module OklahomaMixer
|
|
11
11
|
|
12
12
|
attr_reader :pointer
|
13
13
|
|
14
|
-
|
15
|
-
value = C.read_from_func(:shift, @pointer)
|
16
|
-
block_given? ? yield(value) : value
|
17
|
-
end
|
14
|
+
include Enumerable
|
18
15
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
values << yield(value)
|
16
|
+
def each
|
17
|
+
(0...C.num(pointer)).each do |i|
|
18
|
+
yield C.read_from_func(:val, :no_free, @pointer, i)
|
23
19
|
end
|
24
|
-
values
|
25
20
|
end
|
26
21
|
|
27
22
|
def push(*values)
|
@@ -12,10 +12,14 @@ module OklahomaMixer
|
|
12
12
|
:returns => :pointer
|
13
13
|
func :name => :del,
|
14
14
|
:args => :pointer
|
15
|
-
|
16
|
-
func :name => :
|
17
|
-
:args =>
|
15
|
+
|
16
|
+
func :name => :num,
|
17
|
+
:args => :pointer,
|
18
|
+
:returns => :int
|
19
|
+
func :name => :val,
|
20
|
+
:args => [:pointer, :int, :pointer],
|
18
21
|
:returns => :pointer
|
22
|
+
|
19
23
|
func :name => :push,
|
20
24
|
:args => [:pointer, :pointer, :int]
|
21
25
|
end
|
@@ -39,9 +39,9 @@ module OklahomaMixer
|
|
39
39
|
tune(options)
|
40
40
|
|
41
41
|
warn "mode option supersedes mode argument" if mode and options[:mode]
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
mode_enum = cast_to_enum_int(options.fetch(:mode, mode || "wc"), :mode)
|
43
|
+
@read_only = (mode_enum & cast_to_enum_int("r", :mode)).nonzero?
|
44
|
+
try(:open, path, mode_enum)
|
45
45
|
rescue Exception
|
46
46
|
close if defined?(@db) and @db
|
47
47
|
raise
|
@@ -57,6 +57,10 @@ module OklahomaMixer
|
|
57
57
|
|
58
58
|
attr_reader :path
|
59
59
|
|
60
|
+
def read_only?
|
61
|
+
@read_only
|
62
|
+
end
|
63
|
+
|
60
64
|
def file_size
|
61
65
|
lib.fsiz(@db)
|
62
66
|
end
|
@@ -114,7 +114,7 @@ module OklahomaMixer
|
|
114
114
|
def all(options = { }, &iterator)
|
115
115
|
query(options) do |q|
|
116
116
|
mode = results_mode(options)
|
117
|
-
if
|
117
|
+
if not iterator.nil? and not read_only?
|
118
118
|
results = self
|
119
119
|
callback = lambda { |key_pointer, key_size, doc_map, _|
|
120
120
|
if mode != :docs
|
@@ -125,9 +125,10 @@ module OklahomaMixer
|
|
125
125
|
doc = map.to_hash { |string| cast_to_encoded_string(string) }
|
126
126
|
end
|
127
127
|
flags = case mode
|
128
|
-
when :keys then
|
129
|
-
when :docs then
|
130
|
-
|
128
|
+
when :keys then iterator[key]
|
129
|
+
when :docs then iterator[doc]
|
130
|
+
when :aoh then iterator[doc.merge!(:primary_key => key)]
|
131
|
+
else iterator[[key, doc]]
|
131
132
|
end
|
132
133
|
Array(flags).inject(0) { |returned_flags, flag|
|
133
134
|
returned_flags | case flag.to_s
|
@@ -144,30 +145,16 @@ module OklahomaMixer
|
|
144
145
|
end
|
145
146
|
}
|
146
147
|
}
|
148
|
+
unless lib.qryproc(q.pointer, callback, nil)
|
149
|
+
error_code = lib.ecode(@db)
|
150
|
+
error_message = lib.errmsg(error_code)
|
151
|
+
fail Error::QueryError,
|
152
|
+
"#{error_message} (error code #{error_code})"
|
153
|
+
end
|
154
|
+
results
|
147
155
|
else
|
148
|
-
|
149
|
-
callback = lambda { |key_pointer, key_size, doc_map, _|
|
150
|
-
if mode == :docs
|
151
|
-
results << cast_value_out(doc_map, :no_free)
|
152
|
-
else
|
153
|
-
key = cast_key_out(key_pointer.get_bytes(0, key_size))
|
154
|
-
case mode
|
155
|
-
when :keys
|
156
|
-
results << key
|
157
|
-
when :hoh
|
158
|
-
results[key] = cast_value_out(doc_map, :no_free)
|
159
|
-
when :aoh
|
160
|
-
results << cast_value_out(doc_map, :no_free).
|
161
|
-
merge(:primary_key => key)
|
162
|
-
else
|
163
|
-
results << [key, cast_value_out(doc_map, :no_free)]
|
164
|
-
end
|
165
|
-
end
|
166
|
-
0
|
167
|
-
}
|
156
|
+
query_results(lib.qrysearch(q.pointer), mode, &iterator)
|
168
157
|
end
|
169
|
-
lib.qryproc(q.pointer, callback, nil)
|
170
|
-
results
|
171
158
|
end
|
172
159
|
end
|
173
160
|
|
@@ -177,7 +164,7 @@ module OklahomaMixer
|
|
177
164
|
|
178
165
|
def count(options = { })
|
179
166
|
count = 0
|
180
|
-
all(options) { count += 1 }
|
167
|
+
all(options.merge(:select => :keys)) { count += 1 }
|
181
168
|
count
|
182
169
|
end
|
183
170
|
|
@@ -191,39 +178,31 @@ module OklahomaMixer
|
|
191
178
|
results.per_page = (options[:per_page] || 30).to_i
|
192
179
|
fail Error::QueryError, ":per_page must be >= 1" if results.per_page < 1
|
193
180
|
results.total_entries = 0
|
194
|
-
all(
|
195
|
-
:limit => nil ) ) { |key, value|
|
181
|
+
all(options.merge(:limit => nil)) do |kv|
|
196
182
|
if results.total_entries >= results.offset and
|
197
183
|
results.size < results.per_page
|
198
|
-
|
199
|
-
|
200
|
-
results << key
|
201
|
-
when :docs
|
202
|
-
results << value
|
203
|
-
when :hoh
|
204
|
-
results[key] = value
|
205
|
-
when :aoh
|
206
|
-
results << value.merge(:primary_key => key)
|
184
|
+
if mode == :hoh
|
185
|
+
results[kv.first] = kv.last
|
207
186
|
else
|
208
|
-
results <<
|
187
|
+
results << kv
|
209
188
|
end
|
210
189
|
end
|
211
190
|
results.total_entries += 1
|
212
|
-
|
191
|
+
end
|
213
192
|
results
|
214
193
|
end
|
215
194
|
|
216
|
-
def union(q, *queries)
|
217
|
-
search([q] + queries, lib::SEARCHES[:TDBMSUNION])
|
195
|
+
def union(q, *queries, &iterator)
|
196
|
+
search([q] + queries, lib::SEARCHES[:TDBMSUNION], &iterator)
|
218
197
|
end
|
219
198
|
|
220
|
-
def intersection(q, *queries)
|
221
|
-
search([q] + queries, lib::SEARCHES[:TDBMSISECT])
|
199
|
+
def intersection(q, *queries, &iterator)
|
200
|
+
search([q] + queries, lib::SEARCHES[:TDBMSISECT], &iterator)
|
222
201
|
end
|
223
202
|
alias_method :isect, :intersection
|
224
203
|
|
225
|
-
def difference(q, *queries)
|
226
|
-
search([q] + queries, lib::SEARCHES[:TDBMSDIFF])
|
204
|
+
def difference(q, *queries, &iterator)
|
205
|
+
search([q] + queries, lib::SEARCHES[:TDBMSDIFF], &iterator)
|
227
206
|
end
|
228
207
|
alias_method :diff, :difference
|
229
208
|
|
@@ -361,34 +340,61 @@ module OklahomaMixer
|
|
361
340
|
end
|
362
341
|
end
|
363
342
|
|
364
|
-
def
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
when :docs
|
375
|
-
keys.map { |key| self[cast_key_out(key)] }
|
376
|
-
when :hoh
|
377
|
-
results = { }
|
378
|
-
while key = keys.shift { |k| cast_key_out(k) }
|
379
|
-
results[key] = self[key]
|
343
|
+
def query_results(results, mode, &iterator)
|
344
|
+
keys = ArrayList.new(results)
|
345
|
+
if iterator.nil?
|
346
|
+
results = mode == :hoh ? { } : [ ]
|
347
|
+
iterator = lambda do |key_and_value|
|
348
|
+
if mode == :hoh
|
349
|
+
results[key_and_value.first] = key_and_value.last
|
350
|
+
else
|
351
|
+
results << key_and_value
|
352
|
+
end
|
380
353
|
end
|
381
|
-
results
|
382
|
-
when :aoh
|
383
|
-
keys.map { |key|
|
384
|
-
key = cast_key_out(key)
|
385
|
-
self[key].merge(:primary_key => key)
|
386
|
-
}
|
387
354
|
else
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
355
|
+
results = self
|
356
|
+
end
|
357
|
+
keys.each do |key|
|
358
|
+
flags = Array( case mode
|
359
|
+
when :keys
|
360
|
+
iterator[cast_key_out(key)]
|
361
|
+
when :docs
|
362
|
+
iterator[self[cast_key_out(key)]]
|
363
|
+
when :aoh
|
364
|
+
k = cast_key_out(key)
|
365
|
+
iterator[self[k].merge!(:primary_key => k)]
|
366
|
+
else
|
367
|
+
k = cast_key_out(key)
|
368
|
+
v = self[k]
|
369
|
+
iterator[[k, v]]
|
370
|
+
end ).map { |flag| flag.to_s }
|
371
|
+
if flags.include? "delete"
|
372
|
+
if read_only?
|
373
|
+
warn "attempted delete from a read only query"
|
374
|
+
else
|
375
|
+
delete(key)
|
376
|
+
end
|
377
|
+
elsif v and flags.include? "update"
|
378
|
+
if read_only?
|
379
|
+
warn "attempted update from a read only query"
|
380
|
+
else
|
381
|
+
self[k] = v
|
382
|
+
end
|
383
|
+
end
|
384
|
+
break if flags.include? "break"
|
385
|
+
end
|
386
|
+
results
|
387
|
+
ensure
|
388
|
+
keys.free if keys
|
389
|
+
end
|
390
|
+
|
391
|
+
def search(queries, operation, &iterator)
|
392
|
+
qs = queries.map { |q| query(q) }
|
393
|
+
Utilities.temp_pointer(qs.size) do |pointer|
|
394
|
+
pointer.write_array_of_pointer(qs.map { |q| q.pointer })
|
395
|
+
query_results( lib.metasearch(pointer, qs.size, operation),
|
396
|
+
results_mode(queries.first),
|
397
|
+
&iterator)
|
392
398
|
end
|
393
399
|
ensure
|
394
400
|
if qs
|
@@ -396,7 +402,6 @@ module OklahomaMixer
|
|
396
402
|
q.free
|
397
403
|
end
|
398
404
|
end
|
399
|
-
keys.free if keys
|
400
405
|
end
|
401
406
|
end
|
402
407
|
end
|
@@ -90,6 +90,9 @@ module OklahomaMixer
|
|
90
90
|
:args => [:pointer, :string, INDEXES],
|
91
91
|
:returns => :bool
|
92
92
|
|
93
|
+
func :name => :qrysearch,
|
94
|
+
:args => :pointer,
|
95
|
+
:returns => :pointer
|
93
96
|
call :name => :TDBQRYPROC,
|
94
97
|
:args => [:pointer, :int, :pointer, :pointer],
|
95
98
|
:returns => :int
|
data/test/shared/tuning_tests.rb
CHANGED
@@ -66,6 +66,15 @@ module TuningTests
|
|
66
66
|
"A warning was not issued for an option mode with a mode argument" )
|
67
67
|
end
|
68
68
|
|
69
|
+
def test_r_mode_marks_a_database_as_read_only
|
70
|
+
db do |wc| # default mode: wc
|
71
|
+
assert(!wc.read_only?, "A write/create database was marked read only")
|
72
|
+
end
|
73
|
+
db("r") do |r|
|
74
|
+
assert(r.read_only?, "A read only was not marked as such")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
69
78
|
def test_an_unknown_mode_triggers_a_warning
|
70
79
|
warning = capture_stderr do
|
71
80
|
db("wcu") do
|
@@ -114,6 +114,16 @@ class TestQuery < Test::Unit::TestCase
|
|
114
114
|
results.sort_by { |doc| doc.size } )
|
115
115
|
end
|
116
116
|
|
117
|
+
def test_all_can_pass_key_in_document_to_a_passed_block_and_return_self
|
118
|
+
load_simple_data
|
119
|
+
results = [ ]
|
120
|
+
assert_equal(@db, @db.all(:return => :aoh) { |kv| results << kv })
|
121
|
+
assert_equal( [ {:primary_key => "pk2"},
|
122
|
+
{ :primary_key => "pk1",
|
123
|
+
"a" => "1", "b" => "2", "c" => "3" } ],
|
124
|
+
results.sort_by { |doc| doc.size } )
|
125
|
+
end
|
126
|
+
|
117
127
|
def test_all_with_a_block_does_not_modify_records_by_default
|
118
128
|
load_simple_data
|
119
129
|
assert_equal(@db, @db.all { })
|
@@ -159,6 +169,49 @@ class TestQuery < Test::Unit::TestCase
|
|
159
169
|
assert_match((%w[pk1 pk2] - results).first, @db.keys.first)
|
160
170
|
end
|
161
171
|
|
172
|
+
def test_all_methods_can_control_what_is_passed_to_the_block
|
173
|
+
load_simple_data
|
174
|
+
[ [{:select => :keys}, "pk1"],
|
175
|
+
[{:select => :docs}, {"a" => "1", "b" => "2", "c" => "3"}],
|
176
|
+
[ {:return => :aoa}, [ "pk1",
|
177
|
+
{"a" => "1", "b" => "2", "c" => "3"} ] ],
|
178
|
+
[ {:return => :hoh}, [ "pk1",
|
179
|
+
{"a" => "1", "b" => "2", "c" => "3"} ] ],
|
180
|
+
[ {:return => :aoh}, { :primary_key => "pk1",
|
181
|
+
"a" => "1",
|
182
|
+
"b" => "2",
|
183
|
+
"c" => "3" } ] ].each do |query, results|
|
184
|
+
args = [ ]
|
185
|
+
@db.all(query.merge(:conditions => [:a, :==, 1])) do |kv|
|
186
|
+
args << kv
|
187
|
+
end
|
188
|
+
assert_equal([results], args)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_all_yields_key_value_tuples
|
193
|
+
load_simple_data
|
194
|
+
[ [ {:return => :aoa}, [ "pk1",
|
195
|
+
{"a" => "1", "b" => "2", "c" => "3"} ] ],
|
196
|
+
[ {:return => :hoh}, [ "pk1",
|
197
|
+
{ "a" => "1",
|
198
|
+
"b" => "2",
|
199
|
+
"c" => "3" } ] ] ].each do |query, tuple|
|
200
|
+
yielded = nil
|
201
|
+
@db.all(query.merge(:conditions => [:a, :==, 1])) do |kv|
|
202
|
+
yielded = kv
|
203
|
+
end
|
204
|
+
assert_equal(tuple, yielded)
|
205
|
+
key, value = nil, nil
|
206
|
+
@db.all(query.merge(:conditions => [:a, :==, 1])) do |k, v|
|
207
|
+
key = k
|
208
|
+
value = v
|
209
|
+
end
|
210
|
+
assert_equal(tuple.first, key)
|
211
|
+
assert_equal(tuple.last, value)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
162
215
|
def test_all_fails_with_an_error_for_malformed_conditions
|
163
216
|
assert_raise(OKMixer::Error::QueryError) do
|
164
217
|
@db.all(:conditions => :first) # not column, operator, and expression
|
@@ -757,6 +810,82 @@ class TestQuery < Test::Unit::TestCase
|
|
757
810
|
:order => :first },
|
758
811
|
{:conditions => [:age, :==, 34]} ) )
|
759
812
|
end
|
813
|
+
|
814
|
+
def test_union_can_be_passed_a_block_to_iterate_over_the_results
|
815
|
+
load_condition_data
|
816
|
+
results = [ ]
|
817
|
+
assert_equal( @db,
|
818
|
+
@db.union( { :select => :keys,
|
819
|
+
:conditions => [:first, :ends_with?, "es"],
|
820
|
+
:order => :first },
|
821
|
+
{:conditions => [:age, :==, 34]} ) { |k|
|
822
|
+
results << k
|
823
|
+
} )
|
824
|
+
assert_equal(%w[dana james], results)
|
825
|
+
end
|
826
|
+
|
827
|
+
def test_union_with_a_block_can_update_records
|
828
|
+
load_condition_data
|
829
|
+
assert_equal( @db,
|
830
|
+
@db.union( { :conditions => [:first, :ends_with?, "es"],
|
831
|
+
:order => :first },
|
832
|
+
{:conditions => [:age, :==, 34]} ) { |k, v|
|
833
|
+
if k == "dana"
|
834
|
+
v["salutation"] = "Mrs." # add
|
835
|
+
v["middle"] = "AL" # update
|
836
|
+
v.delete("age") # delete
|
837
|
+
:update
|
838
|
+
end
|
839
|
+
} )
|
840
|
+
assert_equal( { "salutation" => "Mrs.",
|
841
|
+
"first" => "Dana",
|
842
|
+
"middle" => "AL",
|
843
|
+
"last" => "Gray" }, @db["dana"] )
|
844
|
+
end
|
845
|
+
|
846
|
+
def test_union_with_a_block_can_delete_records
|
847
|
+
load_condition_data
|
848
|
+
results = [ ]
|
849
|
+
assert_equal( @db,
|
850
|
+
@db.union( { :select => :keys,
|
851
|
+
:conditions => [:first, :ends_with?, "es"],
|
852
|
+
:order => :first },
|
853
|
+
{:conditions => [:age, :==, 34]} ) { |k|
|
854
|
+
:delete
|
855
|
+
} )
|
856
|
+
assert_equal(1, @db.size)
|
857
|
+
assert_equal(%w[jim], @db.keys)
|
858
|
+
end
|
859
|
+
|
860
|
+
def test_union_with_a_block_can_end_the_query
|
861
|
+
load_condition_data
|
862
|
+
results = [ ]
|
863
|
+
assert_equal( @db,
|
864
|
+
@db.union( { :select => :keys,
|
865
|
+
:conditions => [:first, :ends_with?, "es"],
|
866
|
+
:order => :first },
|
867
|
+
{:conditions => [:age, :==, 34]} ) { |k|
|
868
|
+
results << k
|
869
|
+
:break
|
870
|
+
} )
|
871
|
+
assert_equal(%w[dana], results)
|
872
|
+
end
|
873
|
+
|
874
|
+
def test_union_with_a_block_can_combine_flags
|
875
|
+
load_condition_data
|
876
|
+
results = [ ]
|
877
|
+
assert_equal( @db,
|
878
|
+
@db.union( { :select => :keys,
|
879
|
+
:conditions => [:first, :ends_with?, "es"],
|
880
|
+
:order => :first },
|
881
|
+
{:conditions => [:age, :==, 34]} ) { |k|
|
882
|
+
results << k
|
883
|
+
%w[delete break]
|
884
|
+
} )
|
885
|
+
assert_equal(%w[dana], results)
|
886
|
+
assert_nil(@db["dana"])
|
887
|
+
assert_equal(2, @db.size)
|
888
|
+
end
|
760
889
|
|
761
890
|
def test_intersection_returns_the_set_intersection_of_multiple_queries
|
762
891
|
load_condition_data
|
@@ -798,6 +927,86 @@ class TestQuery < Test::Unit::TestCase
|
|
798
927
|
:order => :first },
|
799
928
|
{:conditions => [:last, :==, "Gray"]} ) )
|
800
929
|
end
|
930
|
+
|
931
|
+
def test_intersection_can_be_passed_a_block_to_iterate_over_the_results
|
932
|
+
load_condition_data
|
933
|
+
results = [ ]
|
934
|
+
assert_equal( @db,
|
935
|
+
@db.isect( { :select => :keys,
|
936
|
+
:return => :hoh,
|
937
|
+
:conditions => [:first, :include?, "a"],
|
938
|
+
:order => :first },
|
939
|
+
{:conditions => [:last, :==, "Gray"]} ) { |k|
|
940
|
+
results << k
|
941
|
+
} )
|
942
|
+
assert_equal(%w[dana james], results)
|
943
|
+
end
|
944
|
+
|
945
|
+
def test_intersection_with_a_block_can_update_records
|
946
|
+
load_condition_data
|
947
|
+
assert_equal( @db,
|
948
|
+
@db.isect( { :return => :hoh,
|
949
|
+
:conditions => [:first, :include?, "a"],
|
950
|
+
:order => :first },
|
951
|
+
{:conditions => [:last, :==, "Gray"]} ) { |k, v|
|
952
|
+
if k == "dana"
|
953
|
+
v["salutation"] = "Mrs." # add
|
954
|
+
v["middle"] = "AL" # update
|
955
|
+
v.delete("age") # delete
|
956
|
+
:update
|
957
|
+
end
|
958
|
+
} )
|
959
|
+
assert_equal( { "salutation" => "Mrs.",
|
960
|
+
"first" => "Dana",
|
961
|
+
"middle" => "AL",
|
962
|
+
"last" => "Gray" }, @db["dana"] )
|
963
|
+
end
|
964
|
+
|
965
|
+
def test_intersection_with_a_block_can_delete_records
|
966
|
+
load_condition_data
|
967
|
+
assert_equal( @db,
|
968
|
+
@db.isect( { :select => :keys,
|
969
|
+
:return => :hoh,
|
970
|
+
:conditions => [:first, :include?, "a"],
|
971
|
+
:order => :first },
|
972
|
+
{:conditions => [:last, :==, "Gray"]} ) { |k|
|
973
|
+
:delete
|
974
|
+
} )
|
975
|
+
assert_equal(1, @db.size)
|
976
|
+
assert_equal(%w[jim], @db.keys)
|
977
|
+
end
|
978
|
+
|
979
|
+
def test_intersection_with_a_block_can_end_the_query
|
980
|
+
load_condition_data
|
981
|
+
results = [ ]
|
982
|
+
assert_equal( @db,
|
983
|
+
@db.isect( { :select => :keys,
|
984
|
+
:return => :hoh,
|
985
|
+
:conditions => [:first, :include?, "a"],
|
986
|
+
:order => :first },
|
987
|
+
{:conditions => [:last, :==, "Gray"]} ) { |k|
|
988
|
+
results << k
|
989
|
+
:break
|
990
|
+
} )
|
991
|
+
assert_equal(%w[dana], results)
|
992
|
+
end
|
993
|
+
|
994
|
+
def test_intersection_with_a_block_can_combine_flags
|
995
|
+
load_condition_data
|
996
|
+
results = [ ]
|
997
|
+
assert_equal( @db,
|
998
|
+
@db.isect( { :select => :keys,
|
999
|
+
:return => :hoh,
|
1000
|
+
:conditions => [:first, :include?, "a"],
|
1001
|
+
:order => :first },
|
1002
|
+
{:conditions => [:last, :==, "Gray"]} ) { |k|
|
1003
|
+
results << k
|
1004
|
+
%w[delete break]
|
1005
|
+
} )
|
1006
|
+
assert_equal(%w[dana], results)
|
1007
|
+
assert_nil(@db["dana"])
|
1008
|
+
assert_equal(2, @db.size)
|
1009
|
+
end
|
801
1010
|
|
802
1011
|
def test_difference_returns_the_set_difference_of_multiple_queries
|
803
1012
|
load_condition_data
|
@@ -836,6 +1045,145 @@ class TestQuery < Test::Unit::TestCase
|
|
836
1045
|
{:conditions => [:first, :==, "Jim"]} ) )
|
837
1046
|
end
|
838
1047
|
|
1048
|
+
def test_difference_can_be_passed_a_block_to_iterate_over_the_results
|
1049
|
+
load_condition_data
|
1050
|
+
results = [ ]
|
1051
|
+
assert_equal( @db,
|
1052
|
+
@db.diff( { :select => :keys,
|
1053
|
+
:conditions => [:last, :==, "Gray"],
|
1054
|
+
:order => :first },
|
1055
|
+
{:conditions => [:first, :==, "Jim"]} ) { |k|
|
1056
|
+
results << k
|
1057
|
+
} )
|
1058
|
+
assert_equal(%w[dana james], results)
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
def test_difference_with_a_block_can_update_records
|
1062
|
+
load_condition_data
|
1063
|
+
assert_equal( @db,
|
1064
|
+
@db.diff( { :conditions => [:last, :==, "Gray"],
|
1065
|
+
:order => :first },
|
1066
|
+
{:conditions => [:first, :==, "Jim"]} ) { |k, v|
|
1067
|
+
if k == "dana"
|
1068
|
+
v["salutation"] = "Mrs." # add
|
1069
|
+
v["middle"] = "AL" # update
|
1070
|
+
v.delete("age") # delete
|
1071
|
+
:update
|
1072
|
+
end
|
1073
|
+
} )
|
1074
|
+
assert_equal( { "salutation" => "Mrs.",
|
1075
|
+
"first" => "Dana",
|
1076
|
+
"middle" => "AL",
|
1077
|
+
"last" => "Gray" }, @db["dana"] )
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
def test_difference_with_a_block_can_delete_records
|
1081
|
+
load_condition_data
|
1082
|
+
assert_equal( @db,
|
1083
|
+
@db.diff( { :select => :keys,
|
1084
|
+
:conditions => [:last, :==, "Gray"],
|
1085
|
+
:order => :first },
|
1086
|
+
{:conditions => [:first, :==, "Jim"]} ) { |k|
|
1087
|
+
:delete
|
1088
|
+
} )
|
1089
|
+
assert_equal(1, @db.size)
|
1090
|
+
assert_equal(%w[jim], @db.keys)
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
def test_difference_with_a_block_can_end_the_query
|
1094
|
+
load_condition_data
|
1095
|
+
results = [ ]
|
1096
|
+
assert_equal( @db,
|
1097
|
+
@db.diff( { :select => :keys,
|
1098
|
+
:conditions => [:last, :==, "Gray"],
|
1099
|
+
:order => :first },
|
1100
|
+
{:conditions => [:first, :==, "Jim"]} ) { |k|
|
1101
|
+
results << k
|
1102
|
+
:break
|
1103
|
+
} )
|
1104
|
+
assert_equal(%w[dana], results)
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
def test_difference_with_a_block_can_combine_flags
|
1108
|
+
load_condition_data
|
1109
|
+
results = [ ]
|
1110
|
+
assert_equal( @db,
|
1111
|
+
@db.diff( { :select => :keys,
|
1112
|
+
:conditions => [:last, :==, "Gray"],
|
1113
|
+
:order => :first },
|
1114
|
+
{:conditions => [:first, :==, "Jim"]} ) { |k|
|
1115
|
+
results << k
|
1116
|
+
%w[delete break]
|
1117
|
+
} )
|
1118
|
+
assert_equal(%w[dana], results)
|
1119
|
+
assert_nil(@db["dana"])
|
1120
|
+
assert_equal(2, @db.size)
|
1121
|
+
end
|
1122
|
+
|
1123
|
+
def test_search_methods_can_control_what_is_passed_to_the_block
|
1124
|
+
load_condition_data
|
1125
|
+
[ [{:select => :keys}, "dana"],
|
1126
|
+
[ {:select => :docs}, { "first" => "Dana",
|
1127
|
+
"middle" => "Ann Leslie",
|
1128
|
+
"last" => "Gray",
|
1129
|
+
"age" => "34" } ],
|
1130
|
+
[ {:return => :aoa}, [ "dana",
|
1131
|
+
{ "first" => "Dana",
|
1132
|
+
"middle" => "Ann Leslie",
|
1133
|
+
"last" => "Gray",
|
1134
|
+
"age" => "34" } ] ],
|
1135
|
+
[ {:return => :hoh}, [ "dana",
|
1136
|
+
{ "first" => "Dana",
|
1137
|
+
"middle" => "Ann Leslie",
|
1138
|
+
"last" => "Gray",
|
1139
|
+
"age" => "34" } ] ],
|
1140
|
+
[ {:return => :aoh}, { :primary_key => "dana",
|
1141
|
+
"first" => "Dana",
|
1142
|
+
"middle" => "Ann Leslie",
|
1143
|
+
"last" => "Gray",
|
1144
|
+
"age" => "34" } ] ].each do |query, results|
|
1145
|
+
%w[union intersection difference].each do |search|
|
1146
|
+
args = [ ]
|
1147
|
+
@db.send( search,
|
1148
|
+
query.merge(:conditions => [:first, :==, "Dana"]) ) do |kv|
|
1149
|
+
args << kv
|
1150
|
+
end
|
1151
|
+
assert_equal([results], args)
|
1152
|
+
end
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
def test_search_methods_yields_key_value_tuples
|
1157
|
+
load_condition_data
|
1158
|
+
[ [ {:return => :aoa}, [ "dana",
|
1159
|
+
{ "first" => "Dana",
|
1160
|
+
"middle" => "Ann Leslie",
|
1161
|
+
"last" => "Gray",
|
1162
|
+
"age" => "34" } ] ],
|
1163
|
+
[ {:return => :hoh}, [ "dana",
|
1164
|
+
{ "first" => "Dana",
|
1165
|
+
"middle" => "Ann Leslie",
|
1166
|
+
"last" => "Gray",
|
1167
|
+
"age" => "34" } ] ] ].each do |query, tuple|
|
1168
|
+
%w[union intersection difference].each do |search|
|
1169
|
+
yielded = nil
|
1170
|
+
@db.send( search,
|
1171
|
+
query.merge(:conditions => [:first, :==, "Dana"]) ) do |kv|
|
1172
|
+
yielded = kv
|
1173
|
+
end
|
1174
|
+
assert_equal(tuple, yielded)
|
1175
|
+
key, value = nil, nil
|
1176
|
+
@db.send( search,
|
1177
|
+
query.merge(:conditions => [:first, :==, "Dana"]) ) do |k, v|
|
1178
|
+
key = k
|
1179
|
+
value = v
|
1180
|
+
end
|
1181
|
+
assert_equal(tuple.first, key)
|
1182
|
+
assert_equal(tuple.last, value)
|
1183
|
+
end
|
1184
|
+
end
|
1185
|
+
end
|
1186
|
+
|
839
1187
|
private
|
840
1188
|
|
841
1189
|
def load_simple_data
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TestReadOnlyQuery < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
# create the database and load some data
|
6
|
+
tdb do |db|
|
7
|
+
db[:pk1] = {:a => 1, :b => 2, :c => 3}
|
8
|
+
db[:pk2] = { }
|
9
|
+
end
|
10
|
+
@db = tdb("r")
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
@db.close
|
15
|
+
remove_db_files
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_all_with_a_block_can_end_the_query
|
19
|
+
results = [ ]
|
20
|
+
assert_equal(@db, @db.all { |k, _| results << k; :break })
|
21
|
+
assert_equal(1, results.size)
|
22
|
+
assert_match(/\Apk[12]\z/, results.first)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_all_with_block_delete_is_ignored_and_triggers_a_warning
|
26
|
+
warning = capture_stderr do
|
27
|
+
assert_equal(@db, @db.all { :delete })
|
28
|
+
end
|
29
|
+
assert_equal(2, @db.size)
|
30
|
+
assert( !warning.empty?,
|
31
|
+
"A warning was not issued for :delete in a read only query" )
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_all_with_block_update_is_ignored_and_triggers_a_warning
|
35
|
+
warning = capture_stderr do
|
36
|
+
assert_equal( @db, @db.all { |k, v|
|
37
|
+
if k == "pk1"
|
38
|
+
v["a"] = "1.1" # change
|
39
|
+
v.delete("c") # remove
|
40
|
+
v[:d] = 4 # add
|
41
|
+
:update
|
42
|
+
end
|
43
|
+
} )
|
44
|
+
end
|
45
|
+
assert_equal({"a" => "1", "b" => "2", "c" => "3"}, @db[:pk1])
|
46
|
+
assert( !warning.empty?,
|
47
|
+
"A warning was not issued for :update in a read only query" )
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_all_methods_can_control_what_is_passed_to_the_block
|
51
|
+
[ [{:select => :keys}, "pk1"],
|
52
|
+
[{:select => :docs}, {"a" => "1", "b" => "2", "c" => "3"}],
|
53
|
+
[ {:return => :aoa}, [ "pk1",
|
54
|
+
{"a" => "1", "b" => "2", "c" => "3"} ] ],
|
55
|
+
[ {:return => :hoh}, [ "pk1",
|
56
|
+
{"a" => "1", "b" => "2", "c" => "3"} ] ],
|
57
|
+
[ {:return => :aoh}, { :primary_key => "pk1",
|
58
|
+
"a" => "1",
|
59
|
+
"b" => "2",
|
60
|
+
"c" => "3" } ] ].each do |query, results|
|
61
|
+
args = [ ]
|
62
|
+
@db.all(query.merge(:conditions => [:a, :==, 1])) do |kv|
|
63
|
+
args << kv
|
64
|
+
end
|
65
|
+
assert_equal([results], args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_all_yields_key_value_tuples
|
70
|
+
[ [ {:return => :aoa}, [ "pk1",
|
71
|
+
{"a" => "1", "b" => "2", "c" => "3"} ] ],
|
72
|
+
[ {:return => :hoh}, [ "pk1",
|
73
|
+
{ "a" => "1",
|
74
|
+
"b" => "2",
|
75
|
+
"c" => "3" } ] ] ].each do |query, tuple|
|
76
|
+
yielded = nil
|
77
|
+
@db.all(query.merge(:conditions => [:a, :==, 1])) do |kv|
|
78
|
+
yielded = kv
|
79
|
+
end
|
80
|
+
assert_equal(tuple, yielded)
|
81
|
+
key, value = nil, nil
|
82
|
+
@db.all(query.merge(:conditions => [:a, :==, 1])) do |k, v|
|
83
|
+
key = k
|
84
|
+
value = v
|
85
|
+
end
|
86
|
+
assert_equal(tuple.first, key)
|
87
|
+
assert_equal(tuple.last, value)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_count_works_on_a_read_only_database
|
92
|
+
assert_equal(@db.size, @db.count)
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_paginate_works_on_a_read_only_database
|
96
|
+
assert_equal( %w[pk1], @db.paginate( :select => :keys,
|
97
|
+
:order => :primary_key,
|
98
|
+
:per_page => 1,
|
99
|
+
:page => 1 ) )
|
100
|
+
assert_equal( %w[pk2], @db.paginate( :select => :keys,
|
101
|
+
:order => :primary_key,
|
102
|
+
:per_page => 1,
|
103
|
+
:page => 2 ) )
|
104
|
+
assert_equal( [ ], @db.paginate( :select => :keys,
|
105
|
+
:order => :primary_key,
|
106
|
+
:per_page => 1,
|
107
|
+
:page => 3 ) )
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_search_with_a_block_can_end_the_query
|
111
|
+
each_search do |search|
|
112
|
+
results = [ ]
|
113
|
+
assert_equal(@db, @db.send(search, :order => :primary_key) { |k, _|
|
114
|
+
results << k; :break
|
115
|
+
})
|
116
|
+
assert_equal(1, results.size)
|
117
|
+
assert_equal("pk1", results.first)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_search_with_block_delete_is_ignored_and_triggers_a_warning
|
122
|
+
each_search do |search|
|
123
|
+
warning = capture_stderr do
|
124
|
+
assert_equal(@db, @db.send(search, :order => :primary_key) { :delete })
|
125
|
+
end
|
126
|
+
assert_equal(2, @db.size)
|
127
|
+
assert( !warning.empty?,
|
128
|
+
"A warning was not issued for :delete in a read only search" )
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_search_with_block_update_is_ignored_and_triggers_a_warning
|
133
|
+
each_search do |search|
|
134
|
+
warning = capture_stderr do
|
135
|
+
assert_equal( @db, @db.send(search, :order => :primary_key) { |k, v|
|
136
|
+
if k == "pk1"
|
137
|
+
v["a"] = "1.1" # change
|
138
|
+
v.delete("c") # remove
|
139
|
+
v[:d] = 4 # add
|
140
|
+
:update
|
141
|
+
end
|
142
|
+
} )
|
143
|
+
end
|
144
|
+
assert_equal({"a" => "1", "b" => "2", "c" => "3"}, @db[:pk1])
|
145
|
+
assert( !warning.empty?,
|
146
|
+
"A warning was not issued for :update in a read only search" )
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def each_search(&test)
|
153
|
+
%w[union intersection difference].each(&test)
|
154
|
+
end
|
155
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oklahoma_mixer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 4
|
8
|
+
- 0
|
9
|
+
version: 0.4.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- James Edward Gray II
|
@@ -9,29 +14,35 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-02-
|
17
|
+
date: 2010-02-20 00:00:00 -06:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: ffi
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
24
|
requirements:
|
21
25
|
- - ">="
|
22
26
|
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 5
|
30
|
+
- 4
|
23
31
|
version: 0.5.4
|
24
|
-
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
25
34
|
- !ruby/object:Gem::Dependency
|
26
35
|
name: rake
|
27
|
-
|
28
|
-
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
38
|
requirements:
|
31
39
|
- - ">="
|
32
40
|
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
33
43
|
version: "0"
|
34
|
-
|
44
|
+
type: :development
|
45
|
+
version_requirements: *id002
|
35
46
|
description: |
|
36
47
|
Oklahoma Mixer is a intended to be an all inclusive wrapper for Tokyo Cabinet.
|
37
48
|
It provides Rubyish interfaces for all database types and supports the full
|
@@ -95,6 +106,7 @@ files:
|
|
95
106
|
- test/table_database/document_storage_test.rb
|
96
107
|
- test/table_database/index_test.rb
|
97
108
|
- test/table_database/query_test.rb
|
109
|
+
- test/table_database/read_only_query_test.rb
|
98
110
|
- test/table_database/table_tuning_test.rb
|
99
111
|
- test/test_helper.rb
|
100
112
|
- AUTHORS.rdoc
|
@@ -120,18 +132,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
132
|
requirements:
|
121
133
|
- - ">="
|
122
134
|
- !ruby/object:Gem::Version
|
135
|
+
segments:
|
136
|
+
- 0
|
123
137
|
version: "0"
|
124
|
-
version:
|
125
138
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
139
|
requirements:
|
127
140
|
- - ">="
|
128
141
|
- !ruby/object:Gem::Version
|
142
|
+
segments:
|
143
|
+
- 0
|
129
144
|
version: "0"
|
130
|
-
version:
|
131
145
|
requirements: []
|
132
146
|
|
133
147
|
rubyforge_project:
|
134
|
-
rubygems_version: 1.3.
|
148
|
+
rubygems_version: 1.3.6
|
135
149
|
signing_key:
|
136
150
|
specification_version: 3
|
137
151
|
summary: An full featured and robust FFI interface to Tokyo Cabinet.
|