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 +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +66 -4
- data/lib/sparkey.rb +5 -0
- data/lib/sparkey/hash_iterator.rb +11 -0
- data/lib/sparkey/hash_reader.rb +10 -8
- data/lib/sparkey/log_iterator.rb +71 -19
- data/lib/sparkey/log_reader.rb +2 -3
- data/lib/sparkey/log_writer.rb +6 -10
- data/lib/sparkey/store.rb +22 -22
- data/sparkey.gemspec +2 -1
- data/spec/sparkey/store_spec.rb +88 -0
- data/spec/sparkey_spec.rb +198 -19
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a41aea2172a4710c84b4992da760068abbdeb439
|
4
|
+
data.tar.gz: 2a325fa579cfca5516681541784f60a9872cd173
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6de97f658c7c89cff32e17078cd8a551d926a98aed2261b2ad56b269a1f7532b2c49c9635e5ea189d526d693839664c2a02ccf90252a6daad499fc2ad55df78
|
7
|
+
data.tar.gz: 2ffeb03f7a7e2dbbf9641f877ce224a04ec36cf2e9cf1eede3bda466a2529f3ad8f0feb48e3a6bb21588962d35ec73e9dff5334bbc340529c1f535548c5425d3
|
data/CHANGELOG.md
ADDED
@@ -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
|
-
|
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
|
-
|
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.
|
29
|
-
5.
|
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
|
data/lib/sparkey.rb
CHANGED
@@ -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"
|
data/lib/sparkey/hash_reader.rb
CHANGED
@@ -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.
|
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::
|
21
|
+
iterator = Sparkey::HashIterator.new(self)
|
23
22
|
|
24
|
-
key_length = key.
|
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
|
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
|
|
data/lib/sparkey/log_iterator.rb
CHANGED
@@ -1,23 +1,26 @@
|
|
1
1
|
class Sparkey::LogIterator
|
2
2
|
include Sparkey::Errors
|
3
3
|
|
4
|
-
def initialize(log_reader
|
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.
|
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
|
20
|
-
handle_status Sparkey::Native.
|
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
|
35
|
+
ptr = FFI::MemoryPointer.new(:int)
|
33
36
|
|
34
|
-
handle_status Sparkey::Native.logiter_keycmp(@log_iter_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
|
-
|
53
|
-
|
54
|
-
|
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,
|
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
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/sparkey/log_reader.rb
CHANGED
@@ -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.
|
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
|
data/lib/sparkey/log_writer.rb
CHANGED
@@ -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.
|
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.
|
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
|
data/lib/sparkey/store.rb
CHANGED
@@ -49,41 +49,31 @@ class Sparkey::Store
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def size
|
52
|
-
hash_reader.
|
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
|
-
|
63
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
data/sparkey.gemspec
CHANGED
@@ -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.
|
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
|
data/spec/sparkey_spec.rb
CHANGED
@@ -8,31 +8,210 @@ describe Sparkey do
|
|
8
8
|
before { @filename = random_filename }
|
9
9
|
after { delete(@filename) }
|
10
10
|
|
11
|
-
it "
|
12
|
-
|
13
|
-
|
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
|
-
|
15
|
+
log_writer.put("first", "Michael")
|
16
|
+
log_writer.flush
|
19
17
|
|
20
|
-
|
18
|
+
hash_writer = Sparkey::HashWriter.new
|
19
|
+
hash_writer.create(@filename)
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
hash_reader = Sparkey::HashReader.new
|
22
|
+
hash_reader.open(@filename)
|
23
|
+
iterator = hash_reader.seek("first")
|
25
24
|
|
26
|
-
|
25
|
+
iterator.get_value.must_equal("Michael")
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
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.
|
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-
|
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
|