Skip to content

Commit 58e28d9

Browse files
committed
First commit of redis-rdb.
0 parents  commit 58e28d9

37 files changed

+1465
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.gem

LICENSE

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Copyright (c) 2012 Daniele Alessandri
2+
3+
Permission is hereby granted, free of charge, to any person
4+
obtaining a copy of this software and associated documentation
5+
files (the "Software"), to deal in the Software without
6+
restriction, including without limitation the rights to use,
7+
copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the
9+
Software is furnished to do so, subject to the following
10+
conditions:
11+
12+
The above copyright notice and this permission notice shall be
13+
included in all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
OTHER DEALINGS IN THE SOFTWARE.

README.md

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# redis-rdb #
2+
3+
This library provides a set of modules and classes that make it easy to handle binary
4+
database dumps generated by [Redis](http://redis.io) (.rdb files) in Ruby.
5+
6+
Currently redis-rdb allows developers to read .rdb files in a streamable flashion
7+
with `RDB::Reader` by providing a set of callbacks. Database objects can optionally
8+
be filtered by database / key / type using custom filters.
9+
10+
```ruby
11+
require 'rdb'
12+
13+
class MyCallbacks
14+
include RDB::ReaderCallbacks
15+
16+
def set(key, value, state)
17+
puts "SET \"#{key}\" \"#{value}\""
18+
end
19+
end
20+
21+
class MyFilter
22+
include RDB::ObjectFilter
23+
24+
KEY_SELECTOR = Regexp.compile(/user:\d+/)
25+
26+
def accept_object?(state)
27+
state.database == 15 && KEY_SELECTOR.match(state.key)
28+
end
29+
end
30+
31+
RDB::Reader.read_file('dump.rdb', callbacks: MyCallbacks.new, filter: MyFilter.new)
32+
```
33+
34+
For more details about callbacks and filters you can take a look at the source code in
35+
[lib/rdb/callbacks.rb](https://github.com/nrk/redis-rdb/lib/rdb/callbacks.rb) and
36+
[lib/rdb/filters.rb](https://github.com/nrk/redis-rdb/lib/rdb/filters.rb).
37+
38+
## Additional notes and credits ##
39+
40+
Right now there is still no documentation about the binary format of .rdb files so we used
41+
the code in [rdb.c](https://github.com/antirez/redis/blob/unstable/src/rdb.c) as the reference
42+
implementation. Credit goes also to [sripathikrishnan](https://github.com/sripathikrishnan),
43+
his work on the [redis-rdb-tools](https://github.com/sripathikrishnan/redis-rdb-tools) Python
44+
library was quite an inspiration for the final design of `RDB::Reader` and we also reused the
45+
.rdb files shipped with his project for testing.
46+
47+
## Dependencies ##
48+
- Ruby >= 1.9.0
49+
50+
## Links ##
51+
52+
### Project ###
53+
- [Source code](https://github.com/nrk/redis-rdb/)
54+
- [Issue tracker](https://github.com/nrk/redis-rdb/issues)
55+
56+
### Related ###
57+
- [Redis](http://redis.io/)
58+
- [redis-rdb-tools](https://github.com/sripathikrishnan/redis-rdb-tools) (Python)
59+
60+
## Author ##
61+
62+
- [Daniele Alessandri](mailto:[email protected]) ([twitter](http://twitter.com/JoL1hAHN))
63+
64+
## License ##
65+
66+
The code for redis-rdb is distributed under the terms of the MIT license (see LICENSE).

Rakefile

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require 'cutest'
2+
3+
task :test do
4+
Cutest.run(Dir['test/test_*.rb'])
5+
end
6+
7+
task :default => :test

examples/read_rdb.rb

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
$:.unshift File.expand_path('../lib', File.dirname(__FILE__))
2+
3+
require 'rdb'
4+
5+
RDB::Reader.read_file('test/rdb/multiple_databases.rdb', callbacks: RDB::DebugCallbacks.new)

lib/rdb.rb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require 'stringio'
2+
require 'rdb/version'
3+
require 'rdb/constants'
4+
require 'rdb/errors'
5+
require 'rdb/lzf'
6+
require 'rdb/filters'
7+
require 'rdb/callbacks'
8+
require 'rdb/reader-state'
9+
require 'rdb/reader'

lib/rdb/callbacks.rb

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
module RDB
2+
module ReaderCallbacks
3+
def start_rdb(rdb_version); end
4+
5+
def end_rdb(); end
6+
7+
def start_database(database); end
8+
9+
def end_database(database); end
10+
11+
def pexpireat(key, expiration, state); end
12+
13+
def set(key, value, state); end
14+
15+
def start_list(key, length, state); end
16+
17+
def rpush(key, value, state); end
18+
19+
def end_list(key, state); end
20+
21+
def start_set(key, length, state); end
22+
23+
def sadd(key, value, state); end
24+
25+
def end_set(key, state); end
26+
27+
def start_sortedset(key, length, state); end
28+
29+
def zadd(key, score, value, state); end
30+
31+
def end_sortedset(key, state); end
32+
33+
def start_hash(key, length, state); end
34+
35+
def hset(key, field, value, state); end
36+
37+
def end_hash(key, state); end
38+
39+
def skip_object(key, state); end
40+
end
41+
42+
class EmptyCallbacks
43+
include ReaderCallbacks
44+
end
45+
46+
class DebugCallbacks
47+
include ReaderCallbacks
48+
49+
def start_rdb(version)
50+
puts "Start RDB file - version #{version}"
51+
end
52+
53+
def end_rdb()
54+
puts "Close RDB file"
55+
end
56+
57+
def start_database(database)
58+
puts "Open database #{database}."
59+
end
60+
61+
def end_database(database)
62+
puts "Close database #{database}."
63+
end
64+
65+
def pexpireat(key, expiration, state)
66+
puts "PEXPIREAT \"#{key}\" \"#{expiration}\""
67+
end
68+
69+
def set(key, value, state)
70+
puts "SET \"#{key}\" \"#{value}\""
71+
end
72+
73+
def start_list(key, length, state)
74+
puts "Start list \"#{key}\" of #{length} items."
75+
end
76+
77+
def rpush(key, value, state)
78+
puts "RPUSH \"#{key}\" \"#{value}\""
79+
end
80+
81+
def end_list(key, state)
82+
puts "End list \"#{key}\"."
83+
end
84+
85+
def start_set(key, length, state)
86+
puts "Start set \"#{key}\" of #{length} members."
87+
end
88+
89+
def sadd(key, value, state)
90+
puts "SADD \"#{key}\" \"#{value}\""
91+
end
92+
93+
def end_set(key, state)
94+
puts "End set \"#{key}\"."
95+
end
96+
97+
def start_sortedset(key, length, state)
98+
puts "Start sortedset \"#{key}\" of #{length} members."
99+
end
100+
101+
def zadd(key, score, value, state)
102+
puts "ZADD \"#{key}\" \"#{score}\" \"#{value}\""
103+
end
104+
105+
def end_sortedset(key, state)
106+
puts "End sortedset \"#{key}\"."
107+
end
108+
109+
def start_hash(key, length, state)
110+
puts "Start hash \"#{key}\" of #{length} members."
111+
end
112+
113+
def hset(key, field, value, state)
114+
puts "HSET \"#{key}\" \"#{field}\" \"#{value}\""
115+
end
116+
117+
def end_hash(key, state)
118+
puts "End hash \"#{key}\"."
119+
end
120+
121+
def skip_object(key, state)
122+
puts "Skipping object for key #{key} of type #{state.mnemonic_type}"
123+
end
124+
end
125+
end

lib/rdb/constants.rb

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
module RDB
2+
module Length
3+
BITS_6 = 0
4+
BITS_14 = 1
5+
BITS_32 = 2
6+
ENCODED = 3
7+
end
8+
9+
module Encoding
10+
INT8 = 0
11+
INT16 = 1
12+
INT32 = 2
13+
LZF = 3
14+
end
15+
16+
module Opcode
17+
EXPIRETIME_MS = 252
18+
EXPIRETIME = 253
19+
SELECTDB = 254
20+
EOF = 255
21+
end
22+
23+
module Type
24+
STRING = 0
25+
LIST = 1
26+
SET = 2
27+
ZSET = 3
28+
HASH = 4
29+
HASH_ZIPMAP = 9
30+
LIST_ZIPLIST = 10
31+
SET_INTSET = 11
32+
ZSET_ZIPLIST = 12
33+
HASH_ZIPLIST = 13
34+
end
35+
end

lib/rdb/errors.rb

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module RDB
2+
class ReaderError < RuntimeError
3+
end
4+
end

lib/rdb/filters.rb

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module RDB
2+
module ObjectFilter
3+
def accept_object?(state)
4+
true
5+
end
6+
end
7+
end

lib/rdb/lzf.rb

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module RDB
2+
module LZF
3+
class << self
4+
def decompress(rdb, compressed_length, expected_length)
5+
ipos = opos = 0
6+
input, output = rdb.read(compressed_length), ' ' * expected_length
7+
8+
while ipos < compressed_length
9+
ctrl = input.getbyte(ipos)
10+
ipos += 1
11+
12+
if ctrl < 32
13+
(ctrl + 1).times do
14+
output.setbyte(opos, input.getbyte(ipos))
15+
ipos += 1
16+
opos += 1
17+
end
18+
else
19+
length = ctrl >> 5
20+
21+
if length == 7
22+
length = length + input.getbyte(ipos)
23+
ipos += 1
24+
end
25+
26+
reference = opos - ((ctrl & 0x1f) << 8) - input.getbyte(ipos) - 1
27+
ipos += 1
28+
29+
(length + 2).times do
30+
output.setbyte(opos, output.getbyte(reference))
31+
reference += 1
32+
opos += 1
33+
end
34+
end
35+
end
36+
37+
if opos != expected_length
38+
raise Exception, "LZF Decompression error: expected length #{expected_length} does not match #{opos}"
39+
end
40+
41+
output
42+
end
43+
end
44+
end
45+
end

lib/rdb/reader-state.rb

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module RDB
2+
class ReaderState
3+
attr_accessor :database, :key, :type, :expiration, :info
4+
attr_reader :callbacks, :filter
5+
6+
def initialize(callbacks = nil, filter = nil)
7+
@callbacks = callbacks || EmptyCallbacks.new
8+
@filter = filter
9+
end
10+
11+
def key_expires?
12+
!@expiration.nil?
13+
end
14+
15+
def mnemonic_type
16+
case @type
17+
when Type::STRING then :string
18+
when Type::SET, Type::SET_INTSET then :set
19+
when Type::LIST, Type::LIST_ZIPLIST then :list
20+
when Type::ZSET, Type::ZSET_ZIPLIST then :sortedset
21+
when Type::HASH, Type::HASH_ZIPMAP, Type::HASH_ZIPLIST then :hash
22+
end
23+
end
24+
end
25+
end

0 commit comments

Comments
 (0)