oklahoma_mixer 0.3.0 → 0.4.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.
@@ -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.