oklahoma_mixer 0.2.0 → 0.3.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.
Files changed (52) hide show
  1. data/CHANGELOG.rdoc +7 -0
  2. data/README.rdoc +4 -0
  3. data/Rakefile +1 -1
  4. data/TODO.rdoc +2 -3
  5. data/lib/oklahoma_mixer.rb +7 -20
  6. data/lib/oklahoma_mixer/array_list.rb +21 -7
  7. data/lib/oklahoma_mixer/array_list/c.rb +9 -1
  8. data/lib/oklahoma_mixer/b_tree_database.rb +25 -9
  9. data/lib/oklahoma_mixer/b_tree_database/c.rb +5 -0
  10. data/lib/oklahoma_mixer/cursor.rb +3 -0
  11. data/lib/oklahoma_mixer/cursor/c.rb +2 -0
  12. data/lib/oklahoma_mixer/error.rb +2 -0
  13. data/lib/oklahoma_mixer/extensible_string.rb +4 -2
  14. data/lib/oklahoma_mixer/extensible_string/c.rb +2 -0
  15. data/lib/oklahoma_mixer/fixed_length_database.rb +11 -3
  16. data/lib/oklahoma_mixer/fixed_length_database/c.rb +2 -0
  17. data/lib/oklahoma_mixer/hash_database.rb +24 -7
  18. data/lib/oklahoma_mixer/hash_database/c.rb +2 -0
  19. data/lib/oklahoma_mixer/hash_map.rb +42 -0
  20. data/lib/oklahoma_mixer/hash_map/c.rb +27 -0
  21. data/lib/oklahoma_mixer/query.rb +73 -0
  22. data/lib/oklahoma_mixer/query/c.rb +51 -0
  23. data/lib/oklahoma_mixer/table_database.rb +402 -0
  24. data/lib/oklahoma_mixer/table_database/c.rb +104 -0
  25. data/lib/oklahoma_mixer/utilities.rb +26 -1
  26. data/test/{b_tree_binary_data_test.rb → b_tree_database/b_tree_binary_data_test.rb} +2 -2
  27. data/test/{b_tree_tuning_test.rb → b_tree_database/b_tree_tuning_test.rb} +2 -2
  28. data/test/{cursor_based_iteration_test.rb → b_tree_database/cursor_based_iteration_test.rb} +2 -2
  29. data/test/{duplicate_storage_test.rb → b_tree_database/duplicate_storage_test.rb} +18 -12
  30. data/test/{binary_data_test.rb → core_database/binary_data_test.rb} +2 -2
  31. data/test/{file_system_test.rb → core_database/file_system_test.rb} +0 -0
  32. data/test/{getting_and_setting_keys_test.rb → core_database/getting_and_setting_keys_test.rb} +31 -60
  33. data/test/{iteration_test.rb → core_database/iteration_test.rb} +2 -2
  34. data/test/{transactions_test.rb → core_database/transactions_test.rb} +0 -0
  35. data/test/core_database/tuning_test.rb +31 -0
  36. data/test/{fixed_length_tuning_test.rb → fixed_length_database/fixed_length_tuning_test.rb} +0 -0
  37. data/test/{getting_and_setting_by_id_test.rb → fixed_length_database/getting_and_setting_by_id_test.rb} +8 -0
  38. data/test/{shared_binary_data.rb → shared/binary_data_tests.rb} +1 -1
  39. data/test/{tuning_test.rb → shared/hash_tuning_tests.rb} +18 -42
  40. data/test/{shared_iteration.rb → shared/iteration_tests.rb} +8 -7
  41. data/test/{key_range_test.rb → shared/key_range_test.rb} +0 -0
  42. data/test/{order_test.rb → shared/order_test.rb} +0 -0
  43. data/test/shared/storage_tests.rb +65 -0
  44. data/test/{top_level_interface_test.rb → shared/top_level_interface_test.rb} +39 -2
  45. data/test/{shared_tuning.rb → shared/tuning_tests.rb} +1 -1
  46. data/test/table_database/document_iteration_test.rb +22 -0
  47. data/test/table_database/document_storage_test.rb +225 -0
  48. data/test/table_database/index_test.rb +57 -0
  49. data/test/table_database/query_test.rb +866 -0
  50. data/test/table_database/table_tuning_test.rb +56 -0
  51. data/test/test_helper.rb +27 -0
  52. metadata +35 -36
@@ -266,6 +266,14 @@ class TestGettingAndSettingByID < Test::Unit::TestCase
266
266
  assert_equal(3, @db.length)
267
267
  end
268
268
 
269
+ def test_empty_returns_true_while_no_pairs_are_in_the_database
270
+ assert(@db.empty?, "An empty database was not detected")
271
+ @db[42] = :value
272
+ assert(!@db.empty?, "A non-empty database was reported empty")
273
+ @db.delete(42)
274
+ assert(@db.empty?, "An empty database was not detected")
275
+ end
276
+
269
277
  def test_each_key_iterates_over_ids
270
278
  @db.update(1 => 100, 2 => 200, 3 => 300)
271
279
  keys = [1, 2, 3]
@@ -1,4 +1,4 @@
1
- module SharedBinaryData
1
+ module BinaryDataTests
2
2
  def test_null_bytes_are_preserved_during_key_iteration
3
3
  @db.each_key do |key|
4
4
  assert_equal(@key, key)
@@ -1,69 +1,66 @@
1
- require "test_helper"
2
- require "shared_tuning"
1
+ require "shared/tuning_tests"
2
+
3
+ module HashTuningTests
4
+ include TuningTests
3
5
 
4
- class TestTuning < Test::Unit::TestCase
5
- def teardown
6
- remove_db_files
7
- end
8
-
9
- include SharedTuning
10
-
11
6
  def test_a_bucket_array_size_can_be_set_with_other_tuning_defaults
12
7
  size = rand(1_000) + 1
13
8
  assert_option_calls([:tune, size, -1, -1, 0xFF], :bnum => size)
14
9
  end
15
-
10
+
16
11
  def test_bucket_array_size_is_converted_to_an_int
17
12
  assert_option_calls([:tune, 42, -1, -1, 0xFF], :bnum => "42")
18
13
  end
19
-
14
+
20
15
  def test_a_record_alignment_power_can_be_set_with_other_tuning_defaults
21
16
  pow = rand(10) + 1
22
17
  assert_option_calls([:tune, 0, pow, -1, 0xFF], :apow => pow)
23
18
  end
24
-
19
+
25
20
  def test_record_alignment_power_is_converted_to_an_int
26
21
  assert_option_calls([:tune, 0, 42, -1, 0xFF], :apow => "42")
27
22
  end
28
-
23
+
29
24
  def test_a_max_free_block_power_can_be_set_with_other_tuning_defaults
30
25
  pow = rand(10) + 1
31
26
  assert_option_calls([:tune, 0, -1, pow, 0xFF], :fpow => pow)
32
27
  end
33
-
28
+
34
29
  def test_max_free_block_power_is_converted_to_an_int
35
30
  assert_option_calls([:tune, 0, -1, 42, 0xFF], :fpow => "42")
36
31
  end
37
-
32
+
38
33
  def test_options_can_be_set_with_other_tuning_defaults
39
34
  assert_option_calls([:tune, 0, -1, -1, 1 | 2], :opts => "ld")
40
35
  end
41
-
36
+
42
37
  def test_options_is_a_string_of_characters_mapped_to_enums_and_ored_together
43
- opts = {"l" => lib::OPTS[:HDBTLARGE], "b" => lib::OPTS[:HDBTBZIP]}
38
+ db_prefix = self.class.name.include?("Table") ? "T" : "H"
39
+ opts = { "l" => lib::OPTS["#{db_prefix}DBTLARGE".to_sym],
40
+ "b" => lib::OPTS["#{db_prefix}DBTBZIP".to_sym]}
44
41
  assert_option_calls( [ :tune, 0, -1, -1,
45
42
  opts.values.inject(0) { |o, v| o | v } ],
46
43
  :opts => opts.keys.join )
47
44
  end
48
-
45
+
49
46
  def test_the_options_string_is_not_case_sensative
50
47
  assert_option_calls([:tune, 0, -1, -1, 1 | 2], :opts => "ld")
51
48
  assert_option_calls([:tune, 0, -1, -1, 1 | 2], :opts => "LD")
52
49
  end
53
-
50
+
54
51
  def test_unknown_options_are_ignored_with_a_warning
55
52
  warning = capture_stderr do
56
53
  assert_option_calls([:tune, 0, -1, -1, 1 | 8], :opts => "ltu")
57
54
  end
58
55
  assert(!warning.empty?, "A warning was not issued for an unknown option")
59
56
  end
60
-
57
+
61
58
  def test_multiple_tuning_parameters_can_be_set_at_the_same_time
62
59
  size = rand(1_000) + 1
63
60
  assert_option_calls( [:tune, size, -1, -1, 1 | 2],
64
61
  :bnum => size, :opts => "ld" )
65
62
  end
66
-
63
+
67
64
  def test_optimize_allows_the_adjustment_of_tune_options_for_an_open_database
68
65
  db do |db|
69
66
  args = capture_args(lib, :optimize) do
@@ -73,25 +70,4 @@ class TestTuning < Test::Unit::TestCase
73
70
  assert_equal([0, 42, -1, 1 | 2], args[1..-1])
74
71
  end
75
72
  end
76
-
77
- def test_limit_for_cached_records_can_be_set
78
- limit = rand(1_000) + 1
79
- assert_option_calls([:setcache, limit], :rcnum => limit)
80
- end
81
-
82
- def test_limit_for_cached_records_is_converted_to_an_int
83
- assert_option_calls([:setcache, 42], :rcnum => "42")
84
- end
85
-
86
- #######
87
- private
88
- #######
89
-
90
- def lib
91
- OKMixer::HashDatabase::C
92
- end
93
-
94
- def db(*args, &block)
95
- hdb(*args, &block)
96
- end
97
73
  end
@@ -1,4 +1,4 @@
1
- module SharedIteration
1
+ module IterationTests
2
2
  def test_each_key_iterates_over_all_keys_in_the_database
3
3
  @db.each_key do |key|
4
4
  @keys.delete(key)
@@ -11,7 +11,7 @@ module SharedIteration
11
11
  assert_instance_of(Array, key_value_array)
12
12
  assert_equal(2, key_value_array.size)
13
13
  key, value = key_value_array
14
- assert_equal(key * 2, value)
14
+ assert_equal(@values[@keys.index(key)], value)
15
15
  @keys.delete(key)
16
16
  @values.delete(value)
17
17
  end
@@ -58,16 +58,17 @@ module SharedIteration
58
58
  assert_kind_of(Enumerable, @db)
59
59
 
60
60
  # examples
61
- assert_equal(%w[b bb], @db.find { |_, value| value == "bb" })
62
- assert_nil(@db.find { |_, value| value == "dd" })
63
- assert_equal(%w[aaa bbb ccc], @db.map { |key, value| key + value }.sort)
64
- assert( @db.any? { |_, value| value.include? "c" },
61
+ assert_equal( ["b", @values[@keys.index("b")]],
62
+ @db.find { |key, _| key == "b" } )
63
+ assert_nil(@db.find { |key, _| key == "d" })
64
+ assert_equal(%w[aaa bbb ccc], @db.map { |key, _| key * 3 }.sort)
65
+ assert( @db.any? { |_, value| value.include?("c") or value.include?("cc") },
65
66
  "A value was not found during iteration" )
66
67
  end
67
68
 
68
69
  def test_delete_if_removes_all_keys_for_which_the_block_returns_true
69
70
  @db.delete_if { |key, _| key != "a" }
70
- assert_equal([%w[a aa]], @db.to_a)
71
+ assert_equal(%w[a], @db.keys)
71
72
  end
72
73
 
73
74
  def test_iterators_return_self_to_match_hash_interface
File without changes
@@ -0,0 +1,65 @@
1
+ module StorageTests
2
+ def test_fetching_a_missing_value_fails_with_an_index_error
3
+ assert_raise(IndexError) do
4
+ @db.fetch(:missing)
5
+ end
6
+ end
7
+
8
+ def test_fetch_can_return_a_default_for_a_missing_value
9
+ assert_equal(42, @db.fetch(:missing, 42))
10
+ end
11
+
12
+ def test_fetch_can_run_a_block_returning_its_result_for_a_missing_value
13
+ assert_equal(:missing, @db.fetch(:missing) { |key| key })
14
+ end
15
+
16
+ def test_fetching_with_a_block_overrides_a_default_and_triggers_a_warning
17
+ warning = capture_stderr do
18
+ assert_equal(42, @db.fetch(:missing, 13) { 42 })
19
+ end
20
+ assert(!warning.empty?, "A warning was not issued for a default and block")
21
+ end
22
+
23
+ def test_store_and_fetch_can_also_be_used_through_the_indexing_brackets
24
+ assert_equal({:a => 1, :b => 2}, @db[:doc] = {:a => 1, :b => 2})
25
+ assert_equal({"a" => "1", "b" => "2"}, @db[:doc])
26
+ end
27
+
28
+ def test_indexing_returns_nil_instead_of_failing_with_index_error
29
+ assert_nil(@db[:missing])
30
+ end
31
+
32
+ def test_a_default_can_be_set_for_indexing_to_return
33
+ @db.default = 42
34
+ assert_equal(42, @db[:missing])
35
+ end
36
+
37
+ def test_the_indexing_default_will_be_run_if_it_is_a_proc
38
+ @db.default = lambda { |key| "is #{key}" }
39
+ assert_equal("is missing", @db[:missing])
40
+ end
41
+
42
+ def test_the_indexing_default_for_a_given_key_can_be_retrieved
43
+ @db.default = lambda { |key| "is #{key}" }
44
+ assert_equal("is ", @db.default)
45
+ assert_equal("is missing", @db.default(:missing))
46
+ end
47
+
48
+ def test_the_indexing_default_can_be_changed
49
+ assert_nil(@db[:missing])
50
+ assert_equal(42, @db.default = 42)
51
+ assert_equal(42, @db[:missing])
52
+ proc = lambda { |key| fail RuntimeError, "%p not found" % [key]}
53
+ assert_equal(proc, @db.default = proc)
54
+ error = assert_raise(RuntimeError) { @db[:missing] }
55
+ assert_equal(":missing not found", error.message)
56
+ end
57
+
58
+ def test_delete_returns_nil_for_a_missing_key
59
+ assert_nil(@db.delete(:missing))
60
+ end
61
+
62
+ def test_delete_can_be_passed_a_block_to_handle_missing_keys
63
+ assert_equal(42, @db.delete(:missing) { 42 })
64
+ end
65
+ end
@@ -5,10 +5,33 @@ class TestTopLevelInterface < Test::Unit::TestCase
5
5
  remove_db_files
6
6
  end
7
7
 
8
+ def test_ok_mixer_is_a_shortcut_for_oklahoma_mixer
9
+ assert_same(OklahomaMixer, OKMixer)
10
+ end
11
+
8
12
  def test_the_version_constant_contains_the_current_version_number
9
13
  assert_match(/\A\d\.\d\.\d\z/, OKMixer::VERSION)
10
14
  end
11
15
 
16
+ def test_the_hash_database_interface_is_autoloaded_as_needed
17
+ assert_loads(%w[hash], %Q{OKMixer.open(#{db_path('tch').inspect}) { }})
18
+ end
19
+
20
+ def test_the_b_tree_database_interface_is_autoloaded_as_needed
21
+ assert_loads( %w[b_tree hash],
22
+ %Q{OKMixer.open(#{db_path('tcb').inspect}) { }} )
23
+ end
24
+
25
+ def test_the_fixed_length_database_interface_is_autoloaded_as_needed
26
+ assert_loads( %w[fixed_length hash],
27
+ %Q{OKMixer.open(#{db_path('tcf').inspect}) { }} )
28
+ end
29
+
30
+ def test_the_table_database_interface_is_autoloaded_as_needed
31
+ assert_loads( %w[hash table],
32
+ %Q{OKMixer.open(#{db_path('tct').inspect}) { }} )
33
+ end
34
+
12
35
  def test_open_of_a_tch_extension_file_creates_a_hash_database
13
36
  OKMixer.open(db_path("tch")) do |db|
14
37
  assert_instance_of(OKMixer::HashDatabase, db)
@@ -27,6 +50,12 @@ class TestTopLevelInterface < Test::Unit::TestCase
27
50
  end
28
51
  end
29
52
 
53
+ def test_open_of_a_tct_extension_file_creates_a_table_database
54
+ OKMixer.open(db_path("tct")) do |db|
55
+ assert_instance_of(OKMixer::TableDatabase, db)
56
+ end
57
+ end
58
+
30
59
  def test_open_of_an_unrecognized_extension_fails_with_an_error
31
60
  assert_raise(ArgumentError) do
32
61
  OKMixer.open("unrecognized")
@@ -53,7 +82,15 @@ class TestTopLevelInterface < Test::Unit::TestCase
53
82
  db.close if db
54
83
  end
55
84
 
56
- def test_ok_mixer_is_a_shortcut_for_oklahoma_mixer
57
- assert_same(OklahomaMixer, OKMixer)
85
+ private
86
+
87
+ def assert_loads(fields, ruby_code)
88
+ run_ruby <<-END_RUBY
89
+ loaded_dbs = lambda { $".grep(/\\b(\\w+)_database\\.rb\\z/) { $1 }.sort }
90
+ before = loaded_dbs.call
91
+ #{ruby_code}
92
+ puts loaded_dbs.call - before
93
+ END_RUBY
94
+ assert_equal(fields, @output.scan(/\w+/))
58
95
  end
59
96
  end
@@ -1,4 +1,4 @@
1
- module SharedTuning
1
+ module TuningTests
2
2
  def test_a_mutex_can_be_activated_as_the_database_is_created
3
3
  assert_option_calls([:setmutex], :mutex => true)
4
4
  end
@@ -0,0 +1,22 @@
1
+ require "test_helper"
2
+ require "shared/iteration_tests"
3
+
4
+ class TestDocumentIteration < Test::Unit::TestCase
5
+ def setup
6
+ @db = tdb
7
+ @keys = %w[a b c]
8
+ @values = @keys.map { |key|
9
+ Hash[*@keys.map { |k| [key + k, "abc".index(k).to_s] }.flatten]
10
+ }
11
+ @keys.zip(@values) do |key, value|
12
+ @db[key] = value
13
+ end
14
+ end
15
+
16
+ def teardown
17
+ @db.close
18
+ remove_db_files
19
+ end
20
+
21
+ include IterationTests
22
+ end
@@ -0,0 +1,225 @@
1
+ require "test_helper"
2
+ require "shared/storage_tests"
3
+
4
+ class TestDocumentStorage < Test::Unit::TestCase
5
+ def setup
6
+ @db = tdb
7
+ end
8
+
9
+ def teardown
10
+ @db.close
11
+ remove_db_files
12
+ end
13
+
14
+ include StorageTests
15
+
16
+ def test_a_document_can_be_stored_and_fetched_from_the_database
17
+ document = {"a" => "1", "b" => "2"}
18
+ assert_equal(document, @db.store("document", document))
19
+ assert_equal(document, @db.fetch("document")) # later
20
+ end
21
+
22
+ def test_both_all_keys_and_values_are_converted_to_strings
23
+ assert_equal({:a => 1, :b => 2}, @db.store(:document, {:a => 1, :b => 2}))
24
+ assert_equal( {"a" => "1", "b" => "2"},
25
+ @db.fetch("document") ) # effectively the same key
26
+ end
27
+
28
+ def test_storing_with_keep_mode_adds_a_document_only_if_it_didnt_already_exist
29
+ original = {"a" => "1", "b" => "2"}
30
+ replacement = {"a" => "1", "c" => "3"}
31
+ assert(@db.store(:doc, original, :keep), "Failed to store a new key")
32
+ assert_equal(original, @db.fetch(:doc))
33
+ assert(!@db.store(:doc, replacement, :keep), "Replaced an existing key")
34
+ assert_equal(original, @db.fetch(:doc))
35
+ end
36
+
37
+ def test_storing_with_cat_mode_concatenates_new_columns_onto_a_document
38
+ original = {"a" => "1", "b" => "2"}
39
+ added = {"a" => "ignored", "c" => "new"}
40
+ assert_equal(original, @db.store(:cols, original, :cat))
41
+ assert_equal(original, @db.fetch(:cols))
42
+ assert_equal(added, @db.store(:cols, added, :cat))
43
+ assert_equal(original.merge("c" => "new"), @db.fetch(:cols))
44
+ end
45
+
46
+ def test_storing_with_add_mode_adds_to_an_existing_value
47
+ assert_equal(0, @db.store(:i, 0, :add))
48
+ assert_equal(1, @db.store(:i, 1, :add))
49
+ assert_equal(2, @db.store(:i, 1, :add))
50
+ assert_equal(1, @db.store(:i, -1, :add))
51
+
52
+ assert_in_delta(1.5, @db.store(:f, 1.5, :add), 2 ** -20)
53
+ assert_in_delta(3.5, @db.store(:f, 2.0, :add), 2 ** -20)
54
+ assert_in_delta(2.5, @db.store(:f, -1.0, :add), 2 ** -20)
55
+ end
56
+
57
+ def test_additions_are_stored_in_a_document
58
+ assert_equal(42, @db.store(:i, 42, :add))
59
+ assert_equal({"_num" => "42"}, @db.fetch(:i))
60
+
61
+ assert_equal(3.14, @db.store(:f, 3.14, :add))
62
+ assert_equal({"_num" => "3.14"}, @db.fetch(:f))
63
+ end
64
+
65
+ def test_additions_can_be_added_to_a_document
66
+ int = {"type" => "Integer"}
67
+ assert_equal(int, @db.store(:i, int))
68
+ assert_equal(42, @db.store(:i, 42, :add))
69
+ assert_equal(int.merge("_num" => "42"), @db.fetch(:i))
70
+
71
+ flt = {"type" => "Float"}
72
+ assert_equal(flt, @db.store(:f, flt))
73
+ assert_equal(3.14, @db.store(:f, 3.14, :add))
74
+ assert_equal(flt.merge("_num" => "3.14"), @db.fetch(:f))
75
+ end
76
+
77
+ def test_adding_to_a_non_added_number_clobbers_the_column
78
+ junk = {"_num" => "junk"}
79
+ assert_equal(junk, @db.store(:i, junk))
80
+ assert_equal(1, @db.store(:i, 1, :add))
81
+ assert_equal({"_num" => "1"}, @db.fetch(:i))
82
+ end
83
+
84
+ def test_addition_types_can_be_switched_as_needed
85
+ assert_equal(1, @db.store(:num, 1, :add))
86
+ assert_equal(2.0, @db.store(:num, 1.0, :add))
87
+ assert_equal(2.1, @db.store(:num, 0.1, :add))
88
+ assert_equal(3, @db.store(:num, 1, :add)) # truncated
89
+ assert_equal(3.5, @db.store(:num, 0.4, :add))
90
+ assert_equal(4, @db.store(:num, 1, :add)) # truncated
91
+ assert_equal({"_num" => "4.5"}, @db.fetch(:num))
92
+ end
93
+
94
+ def test_storing_with_a_block_allows_duplicate_resolution
95
+ @db[:key] = {:a => 1, :b => 2}
96
+ assert_equal( {:b => 2.5, :c => 3},
97
+ @db.store(:key, {:b => 2.5, :c => 3}) { |key, old, new|
98
+ old.merge(new).merge("" => key)
99
+ } )
100
+ assert_equal({"" => "key", "a" => "1", "b" => "2.5", "c" => "3"}, @db[:key])
101
+ end
102
+
103
+ def test_storing_with_a_block_overrides_a_mode_and_triggers_a_warning
104
+ warning = capture_stderr do
105
+ assert_equal({ }, @db.store(:key, { }, :keep) { |key, old, new| })
106
+ end
107
+ assert(!warning.empty?, "A warning was not issued for a mode and block")
108
+ end
109
+
110
+ def test_include_and_aliases_can_be_used_to_check_for_the_existance_of_a_key
111
+ @db[:exist] = {"I" => "exist"}
112
+ %w[include? has_key? key? member?].each do |query|
113
+ assert(@db.send(query, :exist), "Failed to detect an existing key")
114
+ assert(!@db.send(query, :missing), "Failed to detect an missing key")
115
+ end
116
+ end
117
+
118
+ def test_update_sets_multiple_documents_at_once_overwriting_old_documents
119
+ @db[:b] = {"b" => "old"}
120
+ assert_equal( @db, @db.update( :a => {"a" => "new"},
121
+ :b => {"b" => "new"},
122
+ :c => {"c" => "new"}) )
123
+ assert_equal( [ {"a" => "new"},
124
+ {"b" => "new"},
125
+ {"c" => "new"} ], @db.values_at(:a, :b, :c) )
126
+ end
127
+
128
+ def test_update_can_be_passed_a_block_for_handling_duplicates
129
+ @db[:b] = {:keep => 1, :replace => 2}
130
+ assert_equal( @db, @db.update( :a => {:new => 1},
131
+ :b => {:replace => 2.5, :add => 3},
132
+ :c => {:new => 2} ) { |key, old, new|
133
+ old.merge(new).merge("" => key)
134
+ } )
135
+ assert_equal( [ {"new" => "1"},
136
+ { "" => "b",
137
+ "keep" => "1",
138
+ "replace" => "2.5",
139
+ "add" => "3" },
140
+ {"new" => "2"} ], @db.values_at(:a, :b, :c) )
141
+ end
142
+
143
+ def test_values_at_can_be_used_to_retrieve_multiple_documents_at_once
144
+ @db[:a] = {:a => 1}
145
+ @db[:c] = {:c => 2}
146
+ assert_equal([ ], @db.values_at)
147
+ assert_equal( [ {"a" => "1"},
148
+ nil,
149
+ {"c" => "2"} ], @db.values_at(:a, :b, :c) )
150
+ end
151
+
152
+ def test_values_at_supports_defaults
153
+ @db.default = { }
154
+ @db[:a] = {:a => 1}
155
+ @db[:c] = {:c => 2}
156
+ assert_equal( [ {"a" => "1"},
157
+ { },
158
+ {"c" => "2"} ], @db.values_at(:a, :b, :c))
159
+ end
160
+
161
+ def test_keys_returns_all_keys_in_the_database
162
+ @db.update(:a => {:a => 1}, :b => {:b => 2}, :c => {:c => 3})
163
+ assert_equal(%w[a b c], @db.keys.sort)
164
+ end
165
+
166
+ def test_keys_can_take_a_prefix_which_all_returned_keys_must_start_with
167
+ @db.update(:a => {:a => 1}, :ab => {:ab => 2}, :c => {:c => 3})
168
+ assert_equal(%w[a ab], @db.keys(:prefix => :a).sort)
169
+ end
170
+
171
+ def test_keys_can_take_a_limit_of_keys_to_return
172
+ @db.update(:a => {:a => 1}, :b => {:b => 2}, :c => {:c => 3})
173
+ keys = @db.keys(:limit => 2)
174
+ assert_equal(2, keys.size)
175
+ keys.each do |key|
176
+ assert(%w[a b c].include?(key), "Key wasn't in known keys")
177
+ end
178
+ end
179
+
180
+ def test_values_returns_all_values_in_the_database
181
+ @db.update(:a => {:a => 1}, :b => {:b => 2}, :c => {:c => 3})
182
+ assert_equal( [ {"a" => "1"},
183
+ {"b" => "2"},
184
+ {"c" => "3"} ], @db.values.sort_by { |h| h.to_a } )
185
+ end
186
+
187
+ def test_delete_removes_a_document_from_the_database
188
+ @db[:doc] = {"a" => "1"}
189
+ assert_equal({"a" => "1"}, @db.delete(:doc))
190
+ assert_nil(@db[:doc])
191
+ end
192
+
193
+ def test_clear_removes_all_keys_from_the_database
194
+ @db.update(:a => {:a => 1}, :b => {:b => 2}, :c => {:c => 3})
195
+ assert_equal(@db, @db.clear)
196
+ assert_equal([nil, nil, nil], @db.values_at(:a, :b, :c))
197
+ end
198
+
199
+ def test_size_and_length_return_the_count_of_key_value_pairs_in_the_database
200
+ assert_equal(0, @db.size)
201
+ assert_equal(0, @db.length)
202
+ @db.update(:a => {:a => 1}, :b => {:b => 2}, :c => {:c => 3})
203
+ assert_equal(3, @db.size)
204
+ assert_equal(3, @db.length)
205
+ end
206
+
207
+ def test_empty_returns_true_while_no_pairs_are_in_the_database
208
+ assert(@db.empty?, "An empty database was not detected")
209
+ @db[:doc] = {"a" => "1"}
210
+ assert(!@db.empty?, "A non-empty database was reported empty")
211
+ @db.delete(:doc)
212
+ assert(@db.empty?, "An empty database was not detected")
213
+ end
214
+
215
+ def test_generate_unique_id_and_uid_can_be_used_to_manage_increasing_ids
216
+ assert_equal(1, @db.generate_unique_id)
217
+ assert_equal([2, 3, 4, 5], Array.new(4) { @db.uid })
218
+ end
219
+
220
+ def test_uids_are_not_stored_in_documents
221
+ assert_equal(0, @db.size)
222
+ assert_equal(1, @db.uid)
223
+ assert_equal(0, @db.size)
224
+ end
225
+ end