-
-
Notifications
You must be signed in to change notification settings - Fork 39
/
segmented_database.go
184 lines (156 loc) · 4.47 KB
/
segmented_database.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package ethdb
import (
"encoding/binary"
"errors"
"fmt"
"path/filepath"
"strconv"
"sync"
"github.com/gochain-io/gochain/log"
"github.com/syndtr/goleveldb/leveldb"
)
var (
ErrInvalidSegmentFilename = errors.New("invalid segment filename")
)
// SegmentedDatabase represents a database segmented by block number.
// Each segment contains data for a different block number.
type SegmentedDatabase struct {
mu sync.RWMutex
global Database // All data not segmented by block number
segments map[uint64]*DatabaseSegment // Lookup of segments by block number.
log log.Logger // Contextual logger tracking the database path
// Filename of the root database directory.
Path string
// Generates a new instance of Database for a segment.
NewDatabase NewDatabaseFunc
}
// NewSegmentedDatabase returns a new instance of SegmentedDatabase.
func NewSegmentedDatabase(path string) (*SegmentedDatabase, error) {
global, err := NewLDBDatabase(filepath.Join(path, "global"), 0, 0)
if err != nil {
return nil, err
}
return &SegmentedDatabase{
global: global,
segments: make(map[uint64]*DatabaseSegment),
log: log.New("database", path),
Path: path,
}, nil
}
// Close closes all global and segment databases.
func (db *SegmentedDatabase) Close() {
db.global.Close()
for _, seg := range db.segments {
seg.db.Close()
}
}
// Segment finds a segment by block number.
func (db *SegmentedDatabase) Segment(num uint64) *DatabaseSegment {
db.mu.RLock()
seg := db.segments[num]
db.mu.RUnlock()
return seg
}
// CreateSegmentIfNotExists finds or creates a segment by block number.
func (db *SegmentedDatabase) CreateSegmentIfNotExists(num uint64) (*DatabaseSegment, error) {
// Attempt to find segment under read lock.
if seg := db.Segment(num); seg != nil {
return seg, nil
}
// Recheck under write lock and create if it doesn't exist.
db.mu.Lock()
defer db.mu.Unlock()
if seg := db.segments[num]; seg != nil {
return seg, nil
}
// Generate a database for the segment.
sdb, err := db.NewDatabase(NewDatabaseOptions{Path: filepath.Join(db.Path, FormatSegmentFilename(num))})
if err != nil {
return nil, err
}
seg := NewDatabaseSegment(num, sdb)
db.segments[num] = seg
return seg, nil
}
// Put writes the key/value pair to the database.
func (db *SegmentedDatabase) Put(key, value []byte) error {
num, ok := KeyBlockNumber(key)
if !ok {
return db.global.Put(key, value)
}
seg, err := db.CreateSegmentIfNotExists(num)
if err != nil {
return err
}
return seg.db.Put(key, value)
}
// Has returns true if key exists.
func (db *SegmentedDatabase) Has(key []byte) (bool, error) {
num, ok := KeyBlockNumber(key)
if !ok {
return db.global.Has(key)
}
seg := db.Segment(num)
if seg == nil {
return false, nil
}
return seg.db.Has(key)
}
// Get returns the value of a given key.
func (db *SegmentedDatabase) Get(key []byte) ([]byte, error) {
num, ok := KeyBlockNumber(key)
if !ok {
return db.global.Get(key)
}
seg := db.Segment(num)
if seg == nil {
return nil, leveldb.ErrNotFound
}
return seg.db.Get(key)
}
// Delete deletes the key from database.
func (db *SegmentedDatabase) Delete(key []byte) error {
num, ok := KeyBlockNumber(key)
if !ok {
return db.global.Delete(key)
}
seg := db.Segment(num)
if seg == nil {
return nil
}
return seg.db.Delete(key)
}
// DatabaseSegment represents data for single block number.
type DatabaseSegment struct {
blockNumber uint64
db Database
}
// NewDatabaseSegment returns a new instance of DatabaseSegment.
func NewDatabaseSegment(blockNumber uint64, db Database) *DatabaseSegment {
return &DatabaseSegment{blockNumber: blockNumber, db: db}
}
// KeyBlockNumber returns the block number for a given key and returns ok true.
// If the key does not encode the block number then ok is false.
func KeyBlockNumber(key []byte) (num uint64, ok bool) {
if len(key) < 9 {
return 0, false
}
switch key[0] {
case 'h', 't', 'n', 'b', 'r': // copied from core/database_util.go
return binary.BigEndian.Uint64(key[1:9]), true
default:
return 0, false
}
}
// FormatSegmentFilename returns a segment filename.
func FormatSegmentFilename(blockNumber uint64) string {
return fmt.Sprintf("%016x", blockNumber)
}
// ParseSegmentFilename returns a block number for a given segment filename.
func ParseSegmentFilename(filename string) (uint64, error) {
blockNumber, err := strconv.ParseUint(filename, 16, 64)
if err != nil {
return 0, ErrInvalidSegmentFilename
}
return blockNumber, nil
}