sparkey 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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