sparkey 1.0.0 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1d02b7853fe52790fde3dd206f8af7febc4c01f8
4
- data.tar.gz: 34f41f81cbf31b608fa6faed0ba9b252c1e4b1b8
3
+ metadata.gz: a41aea2172a4710c84b4992da760068abbdeb439
4
+ data.tar.gz: 2a325fa579cfca5516681541784f60a9872cd173
5
5
  SHA512:
6
- metadata.gz: 2719e8fd06e6f6d108f5eab7a856a37fafddfafa712b8daff293315293d458c56709a26e6891b68ffdbefd122affad22b8d6e8a79b498e4bbcece8222591c965
7
- data.tar.gz: 81397ce7bf58162b23ad076d2334a1acb63d8064f9cf6bc12da2e71eaa4b3d5ab8fda1c10b84d237437904557c54bd3fee7d529f769694bcb486a647ec0e06a0
6
+ metadata.gz: c6de97f658c7c89cff32e17078cd8a551d926a98aed2261b2ad56b269a1f7532b2c49c9635e5ea189d526d693839664c2a02ccf90252a6daad499fc2ad55df78
7
+ data.tar.gz: 2ffeb03f7a7e2dbbf9641f877ce224a04ec36cf2e9cf1eede3bda466a2529f3ad8f0feb48e3a6bb21588962d35ec73e9dff5334bbc340529c1f535548c5425d3
@@ -0,0 +1,43 @@
1
+ # Sparkey 1.1.0 (November 18, 2013)
2
+ * `Sparkey` API
3
+ * Added `::build_log_filename` to build a log file name from a hash file name.
4
+ * `Sparkey::Store` API
5
+ * Added `#each_from_log` to explicitly iterate using the log file. Will repeatedly yield the key, value, and the type of log entry to the block.
6
+ * Added `#each_from_hash` to explicitly iterate using the hash file. Will repeatedly yield the key and value to the block.
7
+ * Fixed `#get` to return nil if the key is not found.
8
+ * Fixed `#flush` to also re-open the `Sparkey::LogReader` to ensure it does not receive stale entries if used for iteration.
9
+ * Changed `#flush` to no longer accept a block.
10
+ * `Sparkey::HashReader` API
11
+ * Renamed `#size` to `#entry_count`.
12
+ * Added `#collision_count`.
13
+ * `Sparkey::LogIterator` API
14
+ * Added `#new?` to check if the iterator is in a new state.
15
+ * Added `#invalid?` to check if the iterator is in an invalid state.
16
+ * Added `#closed?` to check if the iterator is in a closed state.
17
+ * Added `#entry_put?` to check if the iterator log entry type is a put entry.
18
+ * Added `#entry_delete?` to check if the iterator log entry type is a delete entry.
19
+ * Added `#skip` to skip over a specified number of entries in a log file.
20
+ * Added `#reset` to reset the iterator back to the beginning.
21
+ * Added `#get_key_chunk` to enable retrieving chunks of the key without
22
+ having to store the entire key in memory. Will repeatedly yield to the
23
+ block the key chunk in the requested block size in bytes until the key has
24
+ been fully consumed. Block sizes must be at least 8 bytes.
25
+ * Added `#get_value_chunk` to enable retrieving chunks of the value without
26
+ having to store the entire value in memory. Will repeatedly yield to the
27
+ block the value chunk in the requested block size in bytes until the value has
28
+ been fully consumed. Block sizes must be at least 8 bytes.
29
+ * Fixed `#get_key` to read the maximum key length when consuming the key.
30
+ * Fixed `#get_value` to read the maximum value length when consuming the value.
31
+ * `Sparkey::HashIterator` API
32
+ * Added API to specifically deal with iterating through a hash reader.
33
+ * Supports all existing `Sparkey::LogIterator` API methods.
34
+ * Implement `#next` using the `sparkey_logiter_hashnext` function.
35
+
36
+ # Sparkey 1.0.0 (November 16, 2013)
37
+ * `Sparkey::Store` API
38
+ * `Sparkey::LogReader` API
39
+ * `Sparkey::LogWriter` API
40
+ * `Sparkey::LogIterator` API
41
+ * `Sparkey::HashReader` API
42
+ * `Sparkey::HashWriter` API
43
+ * `Sparkey::Native` API
data/README.md CHANGED
@@ -1,9 +1,12 @@
1
1
  # Sparkey
2
2
 
3
- TODO: Write a gem description
3
+ Ruby [FFI](https://github.com/ffi/ffi) bindings to Spotify's [Sparkey](https://github.com/spotify/sparkey) key-value store.
4
4
 
5
5
  ## Installation
6
6
 
7
+ ### Requirements
8
+ * `libsparkey` ([Github](https://github.com/spotify/sparkey))
9
+
7
10
  Add this line to your application's Gemfile:
8
11
 
9
12
  gem 'sparkey'
@@ -16,14 +19,73 @@ Or install it yourself as:
16
19
 
17
20
  $ gem install sparkey
18
21
 
22
+ ## Design
23
+ This gem aims to expose all of Sparkey's native functionality to Ruby via [FFI](https://github.com/ffi/ffi) bindings.
24
+
25
+ Additionally, it provides higher-level abstractions around the native functionality to provide a more idiomatic Ruby interface.
26
+
27
+ ## Low Level
28
+ `Sparkey::Native` is intended to expose the raw C functions from Sparkey to Ruby via FFI and nothing more.
29
+
30
+ Use this interface if you are adding Ruby classes and methods to expose new Sparkey functionality. A solid understanding of the [FFI](https://github.com/ffi/ffi) API will be required.
31
+
32
+ `Sparkey::Native` should wrap all available functions from [sparkey.h](https://github.com/spotify/sparkey/blob/master/src/sparkey.h). If you find one missing please submit a Pull Request.
33
+
34
+ ## Mid Level
35
+ This gem exposes Ruby-ish versions of most of Sparkey's public data structures and their related functions.
36
+
37
+ Use these interfaces if you need more control over the specific behavior of Sparkey than the `Sparkey::Store` API provides.
38
+
39
+ ## High Level
40
+ The `Sparkey::Store` API provides a very small interface for using Sparkey as a generic key-value store.
41
+
42
+ Use the `Sparkey::Store` API if you only need a fast persistent key-value store and don't want to delve into Sparkey specifics.
43
+
19
44
  ## Usage
45
+ ```ruby
46
+ require "sparkey"
47
+
48
+ # Creates or overwrites the Sparkey file.
49
+ sparkey = Sparkey.create("sparkey_store")
50
+
51
+ # Opens an existing Sparkey file.
52
+ # sparkey = Sparkey.open("sparkey_store")
53
+
54
+ sparkey.put("first", "Hello")
55
+ sparkey.put("second", "World")
56
+ sparkey.put("third", "Goodbye")
57
+ sparkey.flush
58
+
59
+ puts sparkey.size
60
+ #=> 3
61
+
62
+ puts sparkey.get("second")
63
+ #=> "World"
64
+
65
+ sparkey.put("fourth", "Again")
66
+ sparkey.delete("second")
67
+ sparkey.delete("third")
68
+ sparkey.flush
69
+
70
+ puts sparkey.size
71
+ #=> 2
72
+
73
+ collector = Hash.new
74
+ sparkey.each do |key, value|
75
+ collector[key] = value
76
+ end
77
+
78
+ sparkey.close
20
79
 
21
- TODO: Write usage instructions here
80
+ puts collector
81
+ #=> { "first" => "Hello", "fourth" => "Again" }
82
+ ```
22
83
 
23
84
  ## Contributing
24
85
 
25
86
  1. Fork it
26
87
  2. Create your feature branch (`git checkout -b my-new-feature`)
27
88
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
89
+ 4. Ensure all tests pass (`rake test`)
90
+ 5. Push to the branch (`git push origin my-new-feature`)
91
+ 6. Create new Pull Request
@@ -8,6 +8,10 @@ module Sparkey
8
8
  def self.open(filename)
9
9
  Store.open(filename)
10
10
  end
11
+
12
+ def self.build_log_filename(hash_file)
13
+ Sparkey::Native.create_log_filename(hash_file)
14
+ end
11
15
  end
12
16
 
13
17
  require "sparkey/native"
@@ -18,3 +22,4 @@ require "sparkey/log_reader"
18
22
  require "sparkey/log_iterator"
19
23
  require "sparkey/hash_writer"
20
24
  require "sparkey/hash_reader"
25
+ require "sparkey/hash_iterator"
@@ -0,0 +1,11 @@
1
+ class Sparkey::HashIterator < Sparkey::LogIterator
2
+ def initialize(hash_reader)
3
+ @hash_reader = hash_reader
4
+
5
+ super @hash_reader.log_reader
6
+ end
7
+
8
+ def next
9
+ handle_status Sparkey::Native.logiter_hashnext(@log_iter_ptr, @hash_reader.ptr)
10
+ end
11
+ end
@@ -8,32 +8,34 @@ class Sparkey::HashReader
8
8
 
9
9
  handle_status Sparkey::Native.hash_open(ptr, hash_filename, log_filename)
10
10
 
11
- @hash_reader_ptr = ptr.get_pointer(0)
11
+ @hash_reader_ptr = ptr.read_pointer
12
12
  end
13
13
 
14
14
  def close
15
- ptr = FFI::MemoryPointer.new(:pointer)
16
- ptr.put_pointer(0, @hash_reader_ptr)
15
+ ptr = FFI::MemoryPointer.new(:pointer).write_pointer(@hash_reader_ptr)
17
16
 
18
17
  Sparkey::Native.hash_close(ptr)
19
18
  end
20
19
 
21
20
  def seek(key)
22
- iterator = Sparkey::LogIterator.new(log_reader)
21
+ iterator = Sparkey::HashIterator.new(self)
23
22
 
24
- key_length = key.size
25
- key_ptr = FFI::MemoryPointer.new(:uint8, key_length)
26
- key_ptr.put_bytes(0, key)
23
+ key_length = key.bytesize
24
+ key_ptr = FFI::MemoryPointer.new(:uint8, key_length).write_bytes(key)
27
25
 
28
26
  handle_status Sparkey::Native.hash_get(@hash_reader_ptr, key_ptr, key_length, iterator.ptr)
29
27
 
30
28
  iterator
31
29
  end
32
30
 
33
- def size
31
+ def entry_count
34
32
  Sparkey::Native.hash_numentries(@hash_reader_ptr)
35
33
  end
36
34
 
35
+ def collision_count
36
+ Sparkey::Native.hash_numcollisions(@hash_reader_ptr)
37
+ end
38
+
37
39
  def log_reader
38
40
  reader_ptr = Sparkey::Native.hash_getreader(@hash_reader_ptr)
39
41
 
@@ -1,23 +1,26 @@
1
1
  class Sparkey::LogIterator
2
2
  include Sparkey::Errors
3
3
 
4
- def initialize(log_reader, hash_reader = nil)
4
+ def initialize(log_reader)
5
5
  @log_reader = log_reader
6
- @hash_reader = hash_reader
7
6
 
8
7
  ptr = FFI::MemoryPointer.new(:pointer)
9
8
 
10
9
  handle_status Sparkey::Native.logiter_create(ptr, @log_reader.ptr)
11
10
 
12
- @log_iter_ptr = ptr.get_pointer(0)
11
+ @log_iter_ptr = ptr.read_pointer
13
12
  end
14
13
 
15
14
  def next
16
15
  handle_status Sparkey::Native.logiter_next(@log_iter_ptr, @log_reader.ptr)
17
16
  end
18
17
 
19
- def hash_next
20
- handle_status Sparkey::Native.logiter_hashnext(@log_iter_ptr, @hash_reader.ptr)
18
+ def skip(count)
19
+ handle_status Sparkey::Native.logiter_skip(@log_iter_ptr, @log_reader.ptr, count)
20
+ end
21
+
22
+ def reset
23
+ handle_status Sparkey::Native.logiter_reset(@log_iter_ptr, @log_reader.ptr)
21
24
  end
22
25
 
23
26
  def state
@@ -29,17 +32,37 @@ class Sparkey::LogIterator
29
32
  end
30
33
 
31
34
  def <=>(iterator)
32
- ptr = FFI::MemoryPointer.new(:int, 1)
35
+ ptr = FFI::MemoryPointer.new(:int)
33
36
 
34
- handle_status Sparkey::Native.logiter_keycmp(@log_iter_ptr, interator.ptr, @log_reader.ptr, ptr)
37
+ handle_status Sparkey::Native.logiter_keycmp(@log_iter_ptr, iterator.ptr, @log_reader.ptr, ptr)
35
38
 
36
39
  ptr.read_int
37
40
  end
38
41
 
42
+ def new?
43
+ state == :iter_new
44
+ end
45
+
39
46
  def active?
40
47
  state == :iter_active
41
48
  end
42
49
 
50
+ def invalid?
51
+ state == :iter_invalid
52
+ end
53
+
54
+ def closed?
55
+ state == :iter_closed
56
+ end
57
+
58
+ def entry_put?
59
+ type == :entry_put
60
+ end
61
+
62
+ def entry_delete?
63
+ type == :entry_delete
64
+ end
65
+
43
66
  def key_length
44
67
  Sparkey::Native.logiter_keylen(@log_iter_ptr)
45
68
  end
@@ -49,28 +72,57 @@ class Sparkey::LogIterator
49
72
  end
50
73
 
51
74
  def get_key
52
- wanted_key_length = key_length
53
- key_ptr = FFI::MemoryPointer.new(:uint8, wanted_key_length)
54
- actual_key_length_ptr = FFI::MemoryPointer.new(:uint64, 1)
75
+ max_key_length = @log_reader.max_key_length
76
+ buffer_ptr = FFI::MemoryPointer.new(:uint8, max_key_length)
77
+ buffer_length_ptr = FFI::MemoryPointer.new(:uint64)
55
78
 
56
- handle_status Sparkey::Native.logiter_fill_key(@log_iter_ptr, @log_reader.ptr, wanted_key_length, key_ptr, actual_key_length_ptr)
79
+ handle_status Sparkey::Native.logiter_fill_key(@log_iter_ptr, @log_reader.ptr, max_key_length, buffer_ptr, buffer_length_ptr)
57
80
 
58
- key_ptr.read_bytes(actual_key_length_ptr.read_uint64)
81
+ buffer_ptr.read_bytes(buffer_length_ptr.read_uint64)
82
+ end
83
+
84
+ def get_key_chunk(chunk_size = 1024)
85
+ buffer = FFI::Buffer.alloc_out(:uint8, chunk_size)
86
+ buffer_length_ptr = FFI::MemoryPointer.new(:uint64)
87
+
88
+ loop do
89
+ handle_status Sparkey::Native.logiter_keychunk(@log_iter_ptr, @log_reader.ptr, chunk_size, buffer, buffer_length_ptr)
90
+
91
+ buffer_length = buffer_length_ptr.read_uint64
92
+
93
+ break if buffer_length.zero?
94
+
95
+ yield buffer.read_pointer.read_bytes(buffer_length)
96
+ end
59
97
  end
60
98
 
61
99
  def get_value
62
- wanted_value_length = value_length
63
- value_ptr = FFI::MemoryPointer.new(:uint8, wanted_value_length)
64
- actual_value_length_ptr = FFI::MemoryPointer.new(:uint64, 1)
100
+ max_value_length = @log_reader.max_value_length
101
+ buffer_ptr = FFI::MemoryPointer.new(:uint8, max_value_length)
102
+ buffer_length_ptr = FFI::MemoryPointer.new(:uint64)
103
+
104
+ handle_status Sparkey::Native.logiter_fill_value(@log_iter_ptr, @log_reader.ptr, max_value_length, buffer_ptr, buffer_length_ptr)
65
105
 
66
- handle_status Sparkey::Native.logiter_fill_value(@log_iter_ptr, @log_reader.ptr, wanted_value_length, value_ptr, actual_value_length_ptr)
106
+ buffer_ptr.read_bytes(buffer_length_ptr.read_uint64)
107
+ end
108
+
109
+ def get_value_chunk(chunk_size = 1024)
110
+ buffer = FFI::Buffer.alloc_out(:uint8, chunk_size)
111
+ buffer_length_ptr = FFI::MemoryPointer.new(:uint64)
112
+
113
+ loop do
114
+ handle_status Sparkey::Native.logiter_valuechunk(@log_iter_ptr, @log_reader.ptr, chunk_size, buffer, buffer_length_ptr)
67
115
 
68
- value_ptr.read_bytes(actual_value_length_ptr.read_uint64)
116
+ buffer_length = buffer_length_ptr.read_uint64
117
+
118
+ break if buffer_length.zero?
119
+
120
+ yield buffer.read_pointer.read_bytes(buffer_length)
121
+ end
69
122
  end
70
123
 
71
124
  def close
72
- ptr = FFI::MemoryPointer.new(:pointer)
73
- ptr.put_pointer(0, @log_iter_ptr)
125
+ ptr = FFI::MemoryPointer.new(:pointer).write_pointer(@log_iter_ptr)
74
126
 
75
127
  Sparkey::Native.logiter_close(ptr)
76
128
  end
@@ -7,12 +7,11 @@ class Sparkey::LogReader
7
7
 
8
8
  handle_status Sparkey::Native.logreader_open(ptr, log_filename)
9
9
 
10
- @log_reader_ptr = ptr.get_pointer(0)
10
+ @log_reader_ptr = ptr.read_pointer
11
11
  end
12
12
 
13
13
  def close
14
- ptr = FFI::MemoryPointer.new(:pointer)
15
- ptr.put_pointer(0, @log_reader_ptr)
14
+ ptr = FFI::MemoryPointer.new(:pointer).write_pointer(@log_reader_ptr)
16
15
 
17
16
  Sparkey::Native.logreader_close(ptr)
18
17
  end
@@ -7,7 +7,7 @@ class Sparkey::LogWriter
7
7
 
8
8
  handle_status Sparkey::Native.logwriter_create(ptr, log_filename, compression, block_size)
9
9
 
10
- @log_writer_ptr = ptr.get_pointer(0)
10
+ @log_writer_ptr = ptr.read_pointer
11
11
  end
12
12
 
13
13
  def open(filename)
@@ -15,25 +15,22 @@ class Sparkey::LogWriter
15
15
  log_filename = "#{filename}.spl"
16
16
 
17
17
  handle_status Sparkey::Native.logwriter_append(ptr, log_filename)
18
- @log_writer_ptr = ptr.get_pointer(0)
18
+ @log_writer_ptr = ptr.read_pointer
19
19
  end
20
20
 
21
21
  def put(key, value)
22
22
  key_length = key.bytesize
23
- key_ptr = FFI::MemoryPointer.new(:uint8, key_length)
24
- key_ptr.put_bytes(0, key)
23
+ key_ptr = FFI::MemoryPointer.new(:uint8, key_length).write_bytes(key)
25
24
 
26
25
  value_length = value.bytesize
27
- value_ptr = FFI::MemoryPointer.new(:uint8, value_length)
28
- value_ptr.put_bytes(0, value)
26
+ value_ptr = FFI::MemoryPointer.new(:uint8, value_length).write_bytes(value)
29
27
 
30
28
  handle_status Sparkey::Native.logwriter_put(@log_writer_ptr, key_length, key_ptr, value_length, value_ptr)
31
29
  end
32
30
 
33
31
  def delete(key)
34
32
  key_length = key.bytesize
35
- key_ptr = FFI::MemoryPointer.new(:uint8, key_length)
36
- key_ptr.put_bytes(0, key)
33
+ key_ptr = FFI::MemoryPointer.new(:uint8, key_length).write_bytes(key)
37
34
 
38
35
  handle_status Sparkey::Native.logwriter_delete(@log_writer_ptr, key_length, key_ptr)
39
36
  end
@@ -43,8 +40,7 @@ class Sparkey::LogWriter
43
40
  end
44
41
 
45
42
  def close
46
- ptr = FFI::MemoryPointer.new(:pointer)
47
- ptr.put_pointer(0, @log_writer_ptr)
43
+ ptr = FFI::MemoryPointer.new(:pointer).write_pointer(@log_writer_ptr)
48
44
 
49
45
  Sparkey::Native.logwriter_close(ptr)
50
46
  end
@@ -49,41 +49,31 @@ class Sparkey::Store
49
49
  end
50
50
 
51
51
  def size
52
- hash_reader.size
52
+ hash_reader.entry_count
53
53
  end
54
54
 
55
55
  def get(key)
56
56
  iterator = hash_reader.seek(key)
57
57
 
58
+ return unless iterator.active?
59
+
58
60
  iterator.get_value
59
61
  end
60
62
 
61
- def each_from_hash
62
- log_reader = hash_reader.log_reader
63
- iterator = Sparkey::LogIterator.new(log_reader, hash_reader)
64
-
65
- loop do
66
- iterator.hash_next
67
-
68
- break unless iterator.active?
63
+ def each_from_hash(&block)
64
+ iterator = Sparkey::HashIterator.new(hash_reader)
65
+ typeless_block = ->(k, v, _){ block.call(k, v) }
69
66
 
70
- yield iterator.get_key, iterator.get_value
71
- end
67
+ each_with_iterator(iterator, &typeless_block)
72
68
 
73
69
  iterator.close
74
70
  end
75
71
  alias_method :each, :each_from_hash
76
72
 
77
- def each_from_log
73
+ def each_from_log(&block)
78
74
  iterator = Sparkey::LogIterator.new(log_reader)
79
75
 
80
- loop do
81
- iterator.next
82
-
83
- break unless iterator.active?
84
-
85
- yield iterator.get_key, iterator.get_value
86
- end
76
+ each_with_iterator(iterator, &block)
87
77
 
88
78
  iterator.close
89
79
  end
@@ -97,12 +87,22 @@ class Sparkey::Store
97
87
  end
98
88
 
99
89
  def flush
100
- yield self if block_given?
101
-
102
90
  log_writer.flush
103
91
 
104
- # Reset the hash headers
92
+ # Reset to flush cached headers
93
+ log_reader.open(filename)
105
94
  hash_writer.create(filename)
106
95
  hash_reader.open(filename)
107
96
  end
97
+
98
+ private
99
+ def each_with_iterator(iterator)
100
+ loop do
101
+ iterator.next
102
+
103
+ break unless iterator.active?
104
+
105
+ yield iterator.get_key, iterator.get_value, iterator.type
106
+ end
107
+ end
108
108
  end
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "sparkey"
7
- spec.version = "1.0.0"
7
+ spec.version = "1.1.0"
8
8
  spec.authors = ["Adam Tanner"]
9
9
  spec.email = ["adam@adamtanner.org"]
10
10
  spec.description = %{ Ruby FFI bindings for Spotify's Sparkey }
@@ -20,4 +20,5 @@ Gem::Specification.new do |spec|
20
20
  spec.add_dependency "ffi"
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "m"
23
24
  end
@@ -0,0 +1,88 @@
1
+ require "minitest/autorun"
2
+ require "sparkey"
3
+ require "sparkey/testing"
4
+
5
+ describe Sparkey::Store do
6
+ include Sparkey::Testing
7
+
8
+ before { @filename = random_filename }
9
+ after { delete(@filename) }
10
+
11
+ it "creates a Sparkey log file" do
12
+ sparkey = Sparkey::Store.create(@filename, :compression_snappy, 100)
13
+
14
+ File.exists?("#{@filename}.spl").must_equal(true)
15
+ end
16
+
17
+ it "sets values to keys" do
18
+ sparkey = Sparkey.create(@filename)
19
+
20
+ sparkey.put("first", "Michael")
21
+ sparkey.flush
22
+
23
+ sparkey.get("first").must_equal("Michael")
24
+ end
25
+
26
+ it "deletes keys" do
27
+ sparkey = Sparkey.create(@filename)
28
+ sparkey.put("first", "Michael")
29
+ sparkey.put("middle", "Adam")
30
+ sparkey.flush
31
+
32
+ sparkey.delete("first")
33
+ sparkey.flush
34
+
35
+ sparkey.get("first").must_be_nil
36
+ end
37
+
38
+ it "has the size" do
39
+ sparkey = Sparkey.create(@filename)
40
+ sparkey.put("middle", "Adam")
41
+ sparkey.put("last", "Tanner")
42
+ sparkey.flush
43
+
44
+ sparkey.size.must_equal(2)
45
+ end
46
+
47
+ it "supports iterating through the log file" do
48
+ sparkey = Sparkey.create(@filename)
49
+ sparkey.put("first", "Michael")
50
+ sparkey.put("middle", "Adam")
51
+ sparkey.put("last", "Tanner")
52
+ sparkey.delete("middle")
53
+ sparkey.flush
54
+
55
+ collector = []
56
+
57
+ sparkey.each_from_log do |key, value, type|
58
+ collector << [key, value, type]
59
+ end
60
+
61
+ collector.must_equal([
62
+ ["first", "Michael", :entry_put],
63
+ ["middle", "Adam", :entry_put],
64
+ ["last", "Tanner", :entry_put],
65
+ ["middle", "", :entry_delete]
66
+ ])
67
+ end
68
+
69
+ it "supports iterating through the hash file" do
70
+ sparkey = Sparkey.create(@filename)
71
+ sparkey.put("first", "Michael")
72
+ sparkey.put("middle", "Adam")
73
+ sparkey.put("last", "Tanner")
74
+ sparkey.delete("middle")
75
+ sparkey.flush
76
+
77
+ collector = []
78
+
79
+ sparkey.each_from_hash do |key, value|
80
+ collector << [key, value]
81
+ end
82
+
83
+ collector.must_equal([
84
+ ["first", "Michael"],
85
+ ["last", "Tanner"]
86
+ ])
87
+ end
88
+ end
@@ -8,31 +8,210 @@ describe Sparkey do
8
8
  before { @filename = random_filename }
9
9
  after { delete(@filename) }
10
10
 
11
- it "functions as a key value store" do
12
- sparkey = Sparkey::Store.create(@filename, :compression_snappy, 1000)
13
- sparkey.put("first", "Michael")
14
- sparkey.put("second", "Adam")
15
- sparkey.put("third", "Tanner")
16
- sparkey.close
11
+ it "assigns values to keys" do
12
+ log_writer = Sparkey::LogWriter.new
13
+ log_writer.create(@filename, :compression_none, 100)
17
14
 
18
- sparkey = Sparkey::Store.open(@filename)
15
+ log_writer.put("first", "Michael")
16
+ log_writer.flush
19
17
 
20
- sparkey.size.must_equal 3
18
+ hash_writer = Sparkey::HashWriter.new
19
+ hash_writer.create(@filename)
21
20
 
22
- sparkey.get("first").must_equal("Michael")
23
- sparkey.delete("second")
24
- sparkey.flush
21
+ hash_reader = Sparkey::HashReader.new
22
+ hash_reader.open(@filename)
23
+ iterator = hash_reader.seek("first")
25
24
 
26
- sparkey.size.must_equal 2
25
+ iterator.get_value.must_equal("Michael")
27
26
 
28
- hash = Hash.new
29
- sparkey.each do |key, value|
30
- hash[key] = value
27
+ iterator.close
28
+ hash_reader.close
29
+ log_writer.close
30
+ end
31
+
32
+ it "deletes keys" do
33
+ log_writer = Sparkey::LogWriter.new
34
+ log_writer.create(@filename, :compression_none, 100)
35
+ log_writer.put("first", "Michael")
36
+ log_writer.flush
37
+
38
+ log_writer.delete("first")
39
+ log_writer.flush
40
+
41
+ hash_writer = Sparkey::HashWriter.new
42
+ hash_writer.create(@filename)
43
+
44
+ hash_reader = Sparkey::HashReader.new
45
+ hash_reader.open(@filename)
46
+ iterator = hash_reader.seek("first")
47
+
48
+ iterator.must_be(:invalid?)
49
+ hash_reader.entry_count.must_equal(0)
50
+
51
+ iterator.close
52
+ hash_reader.close
53
+ log_writer.close
54
+ end
55
+
56
+ it "has the max key length and max value length" do
57
+ log_writer = Sparkey::LogWriter.new
58
+ log_writer.create(@filename, :compression_none, 100)
59
+ log_writer.put("middle", "Adam")
60
+ log_writer.put("last", "Tanner")
61
+ log_writer.flush
62
+
63
+ log_reader = Sparkey::LogReader.new
64
+ log_reader.open(@filename)
65
+
66
+ log_reader.max_key_length.must_equal(6)
67
+ log_reader.max_value_length.must_equal(6)
68
+
69
+ log_reader.close
70
+ log_writer.close
71
+ end
72
+
73
+ it "builds a log filename from a hash filename" do
74
+ Sparkey.build_log_filename("sparkey.spi").must_equal("sparkey.spl")
75
+ end
76
+
77
+ it "iterates over the log file" do
78
+ log_writer = Sparkey::LogWriter.new
79
+ log_writer.create(@filename, :compression_none, 100)
80
+
81
+ log_writer.put("first", "Michael")
82
+ log_writer.put("middle initial", "A.")
83
+ log_writer.put("last", "Tanner")
84
+ log_writer.delete("middle initial")
85
+ log_writer.flush
86
+
87
+ log_reader = Sparkey::LogReader.new
88
+ log_reader.open(@filename)
89
+
90
+ log_iterator = Sparkey::LogIterator.new(log_reader)
91
+
92
+ log_iterator.must_be(:new?)
93
+
94
+ log_iterator.next
95
+
96
+ log_iterator.must_be(:active?)
97
+ log_iterator.must_be(:entry_put?)
98
+
99
+ log_iterator.key_length.must_equal(5)
100
+ log_iterator.value_length.must_equal(7)
101
+
102
+ log_iterator.get_key.must_equal("first")
103
+ log_iterator.get_value.must_equal("Michael")
104
+
105
+ log_iterator.next
106
+
107
+ key, iterations = "", 0
108
+ log_iterator.get_key_chunk(8) do |chunk|
109
+ iterations += 1
110
+ key << chunk
111
+ end
112
+
113
+ key.must_equal("middle initial")
114
+ iterations.must_equal(2)
115
+
116
+ value, iterations = "", 0
117
+ log_iterator.get_value_chunk(8) do |chunk|
118
+ iterations += 1
119
+ value << chunk
31
120
  end
32
121
 
33
- hash.must_equal(
34
- "first" => "Michael",
35
- "third" => "Tanner"
36
- )
122
+ value.must_equal("A.")
123
+ iterations.must_equal(1)
124
+
125
+ log_iterator.skip(4)
126
+ log_iterator.must_be(:entry_delete?)
127
+
128
+ log_iterator.next
129
+ log_iterator.must_be(:closed?)
130
+
131
+ log_iterator.close
132
+ log_reader.close
133
+ log_writer.close
134
+ end
135
+
136
+ it "iterates over the hash file" do
137
+ log_writer = Sparkey::LogWriter.new
138
+ log_writer.create(@filename, :compression_none, 100)
139
+
140
+ log_writer.put("salutation", "Mr.")
141
+ log_writer.put("first", "Michael")
142
+ log_writer.put("middle", "Adam")
143
+ log_writer.put("last", "Tanner")
144
+ log_writer.delete("first")
145
+ log_writer.flush
146
+
147
+ hash_writer = Sparkey::HashWriter.new
148
+ hash_writer.create(@filename)
149
+
150
+ hash_reader = Sparkey::HashReader.new
151
+ hash_reader.open(@filename)
152
+
153
+ hash_reader.entry_count.must_equal(3)
154
+ hash_reader.collision_count.must_equal(0)
155
+
156
+ hash_iterator = Sparkey::HashIterator.new(hash_reader)
157
+
158
+ hash_iterator.must_be(:new?)
159
+
160
+ hash_iterator.next
161
+ hash_iterator.must_be(:active?)
162
+
163
+ hash_iterator.get_key.must_equal("salutation")
164
+ hash_iterator.get_value.must_equal("Mr.")
165
+
166
+ hash_iterator.next
167
+ hash_iterator.get_key.must_equal("middle")
168
+ hash_iterator.get_value.must_equal("Adam")
169
+
170
+ seek_iterator = hash_reader.seek("last")
171
+ seek_iterator.get_value.must_equal("Tanner")
172
+
173
+ seek_iterator.next
174
+ seek_iterator.must_be(:closed?)
175
+
176
+ invalid_iterator = hash_reader.seek("first")
177
+ invalid_iterator.must_be(:invalid?)
178
+
179
+ invalid_iterator.close
180
+ seek_iterator.close
181
+ hash_iterator.close
182
+ hash_reader.close
183
+ log_writer.close
184
+ end
185
+
186
+ it "compares iterators" do
187
+ log_writer = Sparkey::LogWriter.new
188
+ log_writer.create(@filename, :compression_none, 100)
189
+
190
+ log_writer.put("first", "Michael")
191
+ log_writer.put("middle", "Adam")
192
+ log_writer.put("last", "Tanner")
193
+ log_writer.flush
194
+
195
+ log_reader = Sparkey::LogReader.new
196
+ log_reader.open(@filename)
197
+
198
+ first_iterator = Sparkey::LogIterator.new(log_reader)
199
+ second_iterator = Sparkey::LogIterator.new(log_reader)
200
+
201
+ first_iterator.next
202
+ second_iterator.next
203
+
204
+ comparison = first_iterator <=> second_iterator
205
+ comparison.must_equal(0)
206
+
207
+ first_iterator.next
208
+ comparison = first_iterator <=> second_iterator
209
+ comparison.must_equal(1)
210
+
211
+ first_iterator.reset
212
+ first_iterator.next
213
+ second_iterator.next
214
+ comparison = first_iterator <=> second_iterator
215
+ comparison.must_equal(-1)
37
216
  end
38
217
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sparkey
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Tanner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-17 00:00:00.000000000 Z
11
+ date: 2013-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: m
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description: ' Ruby FFI bindings for Spotify''s Sparkey '
56
70
  email:
57
71
  - adam@adamtanner.org
@@ -60,12 +74,14 @@ extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
62
76
  - .gitignore
77
+ - CHANGELOG.md
63
78
  - Gemfile
64
79
  - LICENSE
65
80
  - README.md
66
81
  - Rakefile
67
82
  - lib/sparkey.rb
68
83
  - lib/sparkey/errors.rb
84
+ - lib/sparkey/hash_iterator.rb
69
85
  - lib/sparkey/hash_reader.rb
70
86
  - lib/sparkey/hash_writer.rb
71
87
  - lib/sparkey/log_iterator.rb
@@ -75,6 +91,7 @@ files:
75
91
  - lib/sparkey/store.rb
76
92
  - lib/sparkey/testing.rb
77
93
  - sparkey.gemspec
94
+ - spec/sparkey/store_spec.rb
78
95
  - spec/sparkey_spec.rb
79
96
  homepage: https://github.com/adamtanner/sparkey
80
97
  licenses:
@@ -101,4 +118,5 @@ signing_key:
101
118
  specification_version: 4
102
119
  summary: Ruby FFI bindings for Spotify's Sparkey
103
120
  test_files:
121
+ - spec/sparkey/store_spec.rb
104
122
  - spec/sparkey_spec.rb