bdb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.textile +95 -0
- data/VERSION +1 -0
- data/ext/bdb.c +3025 -0
- data/ext/bdb.h +104 -0
- data/ext/extconf.rb +91 -0
- data/lib/bdb/base.rb +60 -0
- data/lib/bdb/database.rb +184 -0
- data/lib/bdb/environment.rb +119 -0
- data/lib/bdb/partitioned_database.rb +74 -0
- data/lib/bdb/result_set.rb +41 -0
- data/test/benchmark.rb +31 -0
- data/test/cursor_test.rb +150 -0
- data/test/db_test.rb +157 -0
- data/test/env_test.rb +101 -0
- data/test/simple_test.rb +93 -0
- data/test/stat_test.rb +22 -0
- data/test/test_helper.rb +7 -0
- data/test/txn_test.rb +74 -0
- metadata +82 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'bdb/base'
|
2
|
+
|
3
|
+
class Bdb::PartitionedDatabase < Bdb::Base
|
4
|
+
SEPARATOR = '__'
|
5
|
+
PARTITION_PATTERN = /^[-\w]*$/
|
6
|
+
|
7
|
+
def initialize(base_name, opts = {})
|
8
|
+
@base_name = base_name
|
9
|
+
@partition_by = opts.delete(:partition_by)
|
10
|
+
super(opts)
|
11
|
+
end
|
12
|
+
attr_reader :base_name, :partition_by, :partition
|
13
|
+
|
14
|
+
def databases
|
15
|
+
@databases ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def database(partition = nil)
|
19
|
+
partition ||= self.partition
|
20
|
+
raise 'partition value required' if partition.nil?
|
21
|
+
partition = partition.to_s
|
22
|
+
raise "invalid partition value: #{partition}" unless partition =~ PARTITION_PATTERN
|
23
|
+
|
24
|
+
databases[partition] ||= begin
|
25
|
+
name = [partition, base_name].join(SEPARATOR)
|
26
|
+
database = Bdb::Database.new(name, config)
|
27
|
+
indexes.each do |field, opts|
|
28
|
+
database.index_by(field, opts)
|
29
|
+
end
|
30
|
+
database
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def partitions
|
35
|
+
Dir[environment.path + "/*#{SEPARATOR}#{base_name}"].collect do |file|
|
36
|
+
File.basename(file).split(SEPARATOR).first
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_partition(partition)
|
41
|
+
@partition, old_partition = partition, @partition
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
@partition = old_partition
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
databases.each do |partition, database|
|
49
|
+
database.close
|
50
|
+
end
|
51
|
+
@databases.clear
|
52
|
+
end
|
53
|
+
|
54
|
+
def get(*keys, &block)
|
55
|
+
opts = keys.last.kind_of?(Hash) ? keys.last : {}
|
56
|
+
database(opts[partition_by]).get(*keys, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def set(key, value, opts = {})
|
60
|
+
partition = get_field(partition_by, value)
|
61
|
+
database(partition).set(key, value, opts)
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete(key, opts = {})
|
65
|
+
database(opts[partition_by]).delete(key)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Deletes all records in the database. Beware!
|
69
|
+
def truncate!
|
70
|
+
partitions.each do |partition|
|
71
|
+
database(partition).truncate!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Bdb::ResultSet
|
2
|
+
class LimitReached < Exception; end
|
3
|
+
|
4
|
+
def initialize(opts, &block)
|
5
|
+
@block = block
|
6
|
+
@count = 0
|
7
|
+
@limit = opts[:limit] || opts[:per_page]
|
8
|
+
@limit = @limit.to_i if @limit
|
9
|
+
@offset = opts[:offset] || (opts[:page] ? @limit * (opts[:page] - 1) : 0)
|
10
|
+
@offset = @offset.to_i if @offset
|
11
|
+
|
12
|
+
if @group = opts[:group]
|
13
|
+
raise 'block not supported with group' if @block
|
14
|
+
@results = hash_class.new
|
15
|
+
else
|
16
|
+
@results = []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
attr_reader :count, :group, :limit, :offset, :results
|
20
|
+
|
21
|
+
def hash_class
|
22
|
+
@hash_class ||= defined?(ActiveSupport::OrderedHash) ? ActiveSupport::OrderedHash : Hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def <<(item)
|
26
|
+
@count += 1
|
27
|
+
return if count <= offset
|
28
|
+
|
29
|
+
raise LimitReached if limit and count > limit + offset
|
30
|
+
|
31
|
+
if group
|
32
|
+
key = item.bdb_locator_key
|
33
|
+
group_key = group.is_a?(Fixnum) ? key[0,group] : key
|
34
|
+
(results[group_key] ||= []) << item
|
35
|
+
elsif @block
|
36
|
+
@block.call(item)
|
37
|
+
else
|
38
|
+
results << item
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/test/benchmark.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/bdb/simple'
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
N = 10_000
|
6
|
+
A = [5, 6, "foo", :bar, "bar", {}, :foo, [1,2,4], true, [1,2,3], false, [1], [2], nil].collect {|i| Marshal.dump(i)}
|
7
|
+
|
8
|
+
puts "compare_absolute (#{N} times)"
|
9
|
+
t = Benchmark.measure do
|
10
|
+
N.times do
|
11
|
+
A.sort {|a,b| Bdb::Simple.compare_absolute(Marshal.load(a), Marshal.load(b)) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
puts t
|
15
|
+
|
16
|
+
puts "compare_hash (#{N} times)"
|
17
|
+
t = Benchmark.measure do
|
18
|
+
N.times do
|
19
|
+
A.sort {|a,b| Marshal.load(a).hash <=> Marshal.load(b).hash }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
puts t
|
23
|
+
|
24
|
+
puts "compare_raw (#{N} times)"
|
25
|
+
t = Benchmark.measure do
|
26
|
+
N.times do
|
27
|
+
A.sort
|
28
|
+
end
|
29
|
+
end
|
30
|
+
puts t
|
31
|
+
|
data/test/cursor_test.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CursorTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
mkdir File.join(File.dirname(__FILE__), 'tmp')
|
7
|
+
@db = Bdb::Db.new
|
8
|
+
@db.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'test.db'), nil, Bdb::Db::BTREE, Bdb::DB_CREATE, 0)
|
9
|
+
10.times { |i| @db.put(nil, i.to_s, "data-#{i}", 0)}
|
10
|
+
@cursor = @db.cursor(nil, 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
@cursor.close if @cursor
|
15
|
+
assert(@db.close(0)) if @db
|
16
|
+
rm_rf File.join(File.dirname(__FILE__), 'tmp')
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_get
|
20
|
+
key, value = @cursor.get(nil, nil, Bdb::DB_FIRST)
|
21
|
+
assert_equal '0', key
|
22
|
+
assert_equal 'data-0', value
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_get_range
|
26
|
+
keys = []
|
27
|
+
key, value = @cursor.get("4", nil, Bdb::DB_SET_RANGE)
|
28
|
+
while key and key <= "9"
|
29
|
+
keys << key
|
30
|
+
key, value = @cursor.get(nil, nil, Bdb::DB_NEXT)
|
31
|
+
end
|
32
|
+
|
33
|
+
assert_equal (4..9).collect {|i| i.to_s}, keys
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_pget
|
37
|
+
@db1 = Bdb::Db.new
|
38
|
+
@db1.flags = Bdb::DB_DUPSORT
|
39
|
+
@db1.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'test1.db'), nil, Bdb::Db::HASH, Bdb::DB_CREATE, 0)
|
40
|
+
|
41
|
+
@db.associate(nil, @db1, 0, proc { |sdb, key, data| key.split('-')[0] })
|
42
|
+
|
43
|
+
@db.put(nil, '1234-5678', 'data', 0)
|
44
|
+
@db.put(nil, '5678-1234', 'atad', 0)
|
45
|
+
|
46
|
+
@cursor1 = @db1.cursor(nil, 0)
|
47
|
+
key, pkey, value = @cursor1.pget(nil, nil, Bdb::DB_FIRST)
|
48
|
+
assert_equal '1234', key
|
49
|
+
assert_equal '1234-5678', pkey
|
50
|
+
assert_equal 'data', value
|
51
|
+
|
52
|
+
@cursor1.close
|
53
|
+
@db1.close(0)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_put
|
57
|
+
@cursor.put('10_000', 'data-10_000', Bdb::DB_KEYLAST)
|
58
|
+
value = @db.get(nil, '10_000', nil, 0)
|
59
|
+
assert_equal 'data-10_000', value
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_del
|
63
|
+
key, value = @cursor.get(nil, nil, Bdb::DB_FIRST)
|
64
|
+
value = @db.get(nil, '0', nil, 0)
|
65
|
+
assert_equal '0', key
|
66
|
+
assert_equal 'data-0', value
|
67
|
+
@cursor.del
|
68
|
+
value = @db.get(nil, '0', nil, 0)
|
69
|
+
assert_nil value
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_count
|
73
|
+
@cursor.get(nil, nil, Bdb::DB_FIRST)
|
74
|
+
assert_equal 1, @cursor.count
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_get_all_in_order
|
78
|
+
all = []
|
79
|
+
while pair = @cursor.get(nil, nil, Bdb::DB_NEXT)
|
80
|
+
all << pair.first
|
81
|
+
end
|
82
|
+
assert_equal (0..9).collect {|i| i.to_s}, all
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_get_all_with_btree_compare
|
86
|
+
@db1 = Bdb::Db.new
|
87
|
+
@db1.btree_compare = proc {|db, key1, key2| key2 <=> key1}
|
88
|
+
@db1.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'test1.db'), nil, Bdb::Db::BTREE, Bdb::DB_CREATE, 0)
|
89
|
+
10.times { |i| @db1.put(nil, i.to_s, "data-#{i}", 0)}
|
90
|
+
@cursor1 = @db1.cursor(nil, 0)
|
91
|
+
|
92
|
+
all = []
|
93
|
+
while pair = @cursor1.get(nil, nil, Bdb::DB_NEXT)
|
94
|
+
all << pair.first
|
95
|
+
end
|
96
|
+
assert_equal (0..9).collect {|i| i.to_s}.reverse, all
|
97
|
+
@cursor1.close
|
98
|
+
@db1.close(0)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_btree_compare_raises_if_fixnum_not_returned
|
102
|
+
@db1 = Bdb::Db.new
|
103
|
+
@db1.btree_compare = proc {|db, key1, key2| key1}
|
104
|
+
@db1.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'test1.db'), nil, Bdb::Db::BTREE, Bdb::DB_CREATE, 0)
|
105
|
+
|
106
|
+
assert_raises(TypeError) do
|
107
|
+
@db1.put(nil, "no", "way", 0)
|
108
|
+
@db1.put(nil, "ho", "say", 0)
|
109
|
+
end
|
110
|
+
@db1.close(Bdb::DB_NOSYNC)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_join
|
114
|
+
@personnel_db = Bdb::Db.new
|
115
|
+
@personnel_db.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'personnel_db.db'), nil, Bdb::Db::HASH, Bdb::DB_CREATE, 0)
|
116
|
+
|
117
|
+
@names_db = Bdb::Db.new
|
118
|
+
@names_db.flags = Bdb::DB_DUPSORT
|
119
|
+
@names_db.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'names_db.db'), nil, Bdb::Db::HASH, Bdb::DB_CREATE, 0)
|
120
|
+
|
121
|
+
@jobs_db = Bdb::Db.new
|
122
|
+
@jobs_db.flags = Bdb::DB_DUPSORT
|
123
|
+
@jobs_db.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'jobs_db.db'), nil, Bdb::Db::HASH, Bdb::DB_CREATE, 0)
|
124
|
+
|
125
|
+
[{:ssn => '111-11-1111', :lastname => 'Smith', :job => 'welder'},
|
126
|
+
{:ssn => '222-22-2222', :lastname => 'Jones', :job => 'welder'},
|
127
|
+
{:ssn => '333-33-3333', :lastname => 'Smith', :job => 'painter'}].each do |e|
|
128
|
+
@personnel_db.put(nil, e[:ssn], Marshal.dump([e[:ssn], e[:lastname], e[:job]]), 0)
|
129
|
+
@names_db.put(nil, e[:lastname], e[:ssn], 0)
|
130
|
+
@jobs_db.put(nil, e[:job], e[:ssn], 0)
|
131
|
+
end
|
132
|
+
|
133
|
+
@name_cursor = @names_db.cursor(nil, 0)
|
134
|
+
@name_cursor.get('Smith', nil, Bdb::DB_SET)
|
135
|
+
assert_equal 2, @name_cursor.count
|
136
|
+
@job_cursor = @jobs_db.cursor(nil, 0)
|
137
|
+
@job_cursor.get('welder', nil, Bdb::DB_SET)
|
138
|
+
assert_equal 2, @job_cursor.count
|
139
|
+
@personnel_cursor = @personnel_db.join([@name_cursor, @job_cursor], 0)
|
140
|
+
assert_equal '111-11-1111', @personnel_cursor.get(nil, nil, 0).first
|
141
|
+
|
142
|
+
@personnel_cursor.close
|
143
|
+
@name_cursor.close
|
144
|
+
@job_cursor.close
|
145
|
+
|
146
|
+
@jobs_db.close(0)
|
147
|
+
@names_db.close(0)
|
148
|
+
@personnel_db.close(0)
|
149
|
+
end
|
150
|
+
end
|
data/test/db_test.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class DbTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
mkdir File.join(File.dirname(__FILE__), 'tmp')
|
7
|
+
@db = Bdb::Db.new
|
8
|
+
@db.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'test.db'), nil, Bdb::Db::BTREE, Bdb::DB_CREATE, 0)
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
assert(@db.close(0)) if @db
|
13
|
+
rm_rf File.join(File.dirname(__FILE__), 'tmp')
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_put_and_get
|
17
|
+
@db.put(nil, 'key', 'data', 0)
|
18
|
+
result = @db.get(nil, 'key', nil, 0)
|
19
|
+
assert_equal 'data', result
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_del
|
23
|
+
@db.put(nil, 'key', 'data', 0)
|
24
|
+
result = @db.get(nil, 'key', nil, 0)
|
25
|
+
assert_equal 'data', result
|
26
|
+
@db.del(nil, 'key', 0)
|
27
|
+
result = @db.get(nil, 'key', nil, 0)
|
28
|
+
assert_nil result
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_flags_set_and_get
|
32
|
+
@db1 = Bdb::Db.new
|
33
|
+
@db1.flags = Bdb::DB_DUPSORT
|
34
|
+
assert Bdb::DB_DUPSORT, @db1.flags
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_associate_and_pget
|
38
|
+
@db1 = Bdb::Db.new
|
39
|
+
@db1.flags = Bdb::DB_DUPSORT
|
40
|
+
@db1.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'test1.db'), nil, Bdb::Db::HASH, Bdb::DB_CREATE, 0)
|
41
|
+
|
42
|
+
@db.associate(nil, @db1, 0, proc { |sdb, key, data| key.split('-')[0] })
|
43
|
+
|
44
|
+
@db.put(nil, '1234-5678', 'data', 0)
|
45
|
+
@db.put(nil, '5678-1234', 'atad', 0)
|
46
|
+
|
47
|
+
result = @db.get(nil, '1234-5678', nil, 0)
|
48
|
+
assert_equal 'data', result
|
49
|
+
result = @db1.get(nil, '5678', nil, 0)
|
50
|
+
assert_equal 'atad', result
|
51
|
+
|
52
|
+
result = @db1.pget(nil, '1234', nil, 0)
|
53
|
+
assert_equal ['1234-5678', 'data'], result
|
54
|
+
|
55
|
+
@db1.close(0)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_associate_with_multiple_keys
|
59
|
+
@db1 = Bdb::Db.new
|
60
|
+
@db1.flags = Bdb::DB_DUPSORT
|
61
|
+
@db1.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'test1.db'), nil, Bdb::Db::HASH, Bdb::DB_CREATE, 0)
|
62
|
+
|
63
|
+
@db.associate(nil, @db1, 0, proc { |sdb, key, data| key.split('-') })
|
64
|
+
|
65
|
+
@db.put(nil, '1234-5678', 'data', 0)
|
66
|
+
@db.put(nil, '8765-4321', 'atad', 0)
|
67
|
+
|
68
|
+
result = @db.get(nil, '1234-5678', nil, 0)
|
69
|
+
assert_equal 'data', result
|
70
|
+
result = @db1.get(nil, '5678', nil, 0)
|
71
|
+
assert_equal 'data', result
|
72
|
+
result = @db1.get(nil, '1234', nil, 0)
|
73
|
+
assert_equal 'data', result
|
74
|
+
result = @db1.get(nil, '8765', nil, 0)
|
75
|
+
assert_equal 'atad', result
|
76
|
+
result = @db1.get(nil, '4321', nil, 0)
|
77
|
+
assert_equal 'atad', result
|
78
|
+
|
79
|
+
@db1.close(0)
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_aset_and_aget
|
83
|
+
@db['key'] = 'data'
|
84
|
+
result = @db.get(nil, 'key', nil, 0)
|
85
|
+
assert_equal 'data', result
|
86
|
+
result = @db['key']
|
87
|
+
assert_equal 'data', result
|
88
|
+
@db['key'] = 'data1'
|
89
|
+
result = @db['key']
|
90
|
+
assert_equal 'data1', result
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_get_byteswapped
|
94
|
+
@db.get_byteswapped
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_get_type
|
98
|
+
assert_equal Bdb::Db::BTREE, @db.get_type
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_remove
|
102
|
+
@db1 = Bdb::Db.new
|
103
|
+
@db1.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'other_test.db'), nil, Bdb::Db::BTREE, Bdb::DB_CREATE, 0)
|
104
|
+
@db1.close(0)
|
105
|
+
Bdb::Db.new.remove(File.join(File.dirname(__FILE__), 'tmp', 'other_test.db'), nil, 0)
|
106
|
+
assert !File.exists?(File.join(File.dirname(__FILE__), 'tmp', 'other_test.db'))
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_key_range
|
110
|
+
10.times { |i| @db.put(nil, i.to_s, 'data', 0) }
|
111
|
+
@db.key_range(nil, '2', 0)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_rename
|
115
|
+
@db1 = Bdb::Db.new
|
116
|
+
@db1.open(nil, File.join(File.dirname(__FILE__), 'tmp', 'other_test.db'), nil, Bdb::Db::BTREE, Bdb::DB_CREATE, 0)
|
117
|
+
@db1.close(0)
|
118
|
+
assert Bdb::Db.new.rename(File.join(File.dirname(__FILE__), 'tmp', 'other_test.db'), nil, File.join(File.dirname(__FILE__), 'tmp', 'other2_test.db'), 0)
|
119
|
+
assert File.exists?(File.join(File.dirname(__FILE__), 'tmp', 'other2_test.db'))
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_pagesize_get_and_set
|
123
|
+
@db1 = Bdb::Db.new
|
124
|
+
@db1.pagesize = 1024
|
125
|
+
assert_equal 1024, @db1.pagesize
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_h_ffactor_get_and_set
|
129
|
+
@db1 = Bdb::Db.new
|
130
|
+
@db1.h_ffactor = 5
|
131
|
+
assert_equal 5, @db1.h_ffactor
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_h_nelem_get_and_set
|
135
|
+
@db1 = Bdb::Db.new
|
136
|
+
@db1.h_nelem = 10_000
|
137
|
+
assert_equal 10_000, @db1.h_nelem
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_sync
|
141
|
+
assert @db.sync
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_truncate
|
145
|
+
@db.put(nil, 'key', 'data', 0)
|
146
|
+
result = @db.get(nil, 'key', nil, 0)
|
147
|
+
assert_equal 'data', result
|
148
|
+
@db.truncate(nil)
|
149
|
+
result = @db.get(nil, 'key', nil, 0)
|
150
|
+
assert_nil result
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_compact
|
154
|
+
assert @db.compact(nil, nil, nil, nil, 0)
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|