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.
@@ -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. Add support for Tokyo Tyrant
7
- 2. Ensure Ruby 1.9 compatibility
8
- 3. Write API documentation
9
- 4. Include some higher level abstractions like mixed tables, queues, and shards
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
@@ -1,5 +1,5 @@
1
1
  module OklahomaMixer
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
 
4
4
  autoload :HashDatabase, "oklahoma_mixer/hash_database"
5
5
  autoload :BTreeDatabase, "oklahoma_mixer/b_tree_database"
@@ -11,17 +11,12 @@ module OklahomaMixer
11
11
 
12
12
  attr_reader :pointer
13
13
 
14
- def shift
15
- value = C.read_from_func(:shift, @pointer)
16
- block_given? ? yield(value) : value
17
- end
14
+ include Enumerable
18
15
 
19
- def map
20
- values = [ ]
21
- while value = shift
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 => :shift,
17
- :args => [:pointer, :pointer],
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
- try( :open,
43
- path,
44
- cast_to_enum_int(options.fetch(:mode, mode || "wc"), :mode) )
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 block_given?
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 yield(key)
129
- when :docs then yield(doc)
130
- else yield(key, doc)
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
- results = mode != :hoh ? [ ] : { }
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( options.merge( :select => :keys_and_docs,
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
- case mode
199
- when :keys
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 << [key, value]
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 search(queries, operation)
365
- mode = results_mode(queries.first)
366
- qs = queries.map { |q| query(q) }
367
- keys = ArrayList.new( Utilities.temp_pointer(qs.size) do |pointer|
368
- pointer.write_array_of_pointer(qs.map { |q| q.pointer })
369
- lib.metasearch(pointer, qs.size, operation)
370
- end )
371
- case mode
372
- when :keys
373
- keys.map { |key| cast_key_out(key) }
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
- keys.map { |key|
389
- key = cast_key_out(key)
390
- [key, self[key]]
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
@@ -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
- version: 0.3.0
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-09 00:00:00 -06:00
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
- type: :runtime
18
- version_requirement:
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
- version:
32
+ type: :runtime
33
+ version_requirements: *id001
25
34
  - !ruby/object:Gem::Dependency
26
35
  name: rake
27
- type: :development
28
- version_requirement:
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
- version:
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.5
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.