bdb 0.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.
- 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
|