forked from colinmarc/cdb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcdb.go
181 lines (150 loc) · 3.94 KB
/
cdb.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
/*
Package cdb provides a native implementation of cdb, a constant key/value
database with some very nice properties.
For more information on cdb, see the original design doc at http://cr.yp.to/cdb.html.
*/
package cdb
import (
"bytes"
"encoding/binary"
"io"
"os"
)
const indexSize = 256 * 8
type index [256]table
// CDB represents an open CDB database. It can only be used for reads; to
// create a database, use Writer.
type CDB struct {
reader io.ReaderAt
readerBytes []byte
readerCloser io.Closer
hash func([]byte) uint32
index index
}
type table struct {
offset uint32
length uint32
}
// Open opens an existing CDB database at the given path.
func Open(path string) (*CDB, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
return New(f, nil)
}
// New opens a new CDB instance for the given io.ReaderAt. It can only be used
// for reads; to create a database, use Writer. The returned CDB instance is
// thread-safe as long as reader is.
//
// If hash is nil, it will default to the CDB hash function. If a database
// was created with a particular hash function, that same hash function must be
// passed to New, or the database will return incorrect results.
func New(reader io.ReaderAt, hash func([]byte) uint32) (*CDB, error) {
return NewFromReaderWithHasher(reader, hash)
}
func NewFromReaderWithHasher(reader io.ReaderAt, hash func ([]byte) uint32) (*CDB, error) {
cdb := &CDB{reader: reader}
if closer, ok := cdb.reader.(io.Closer); ok {
cdb.readerCloser = closer
}
return cdb.initialize(hash)
}
func NewFromBufferWithHasher(buffer []byte, hash func ([]byte) uint32) (*CDB, error) {
cdb := &CDB{readerBytes: buffer}
return cdb.initialize(hash)
}
func (cdb *CDB) initialize (hash func ([]byte) uint32) (*CDB, error) {
if hash == nil {
hash = CDBHash
}
cdb.hash = hash
err := cdb.readIndex()
if err != nil {
return nil, err
}
return cdb, nil
}
// Get returns the value for a given key, or nil if it can't be found.
func (cdb *CDB) Get(key []byte) ([]byte, error) {
key_0 := *NoEscapeBytes(&key)
hash := cdb.hash(key_0)
return cdb.GetWithHash(key_0, hash)
}
func (cdb *CDB) GetWithHash(key []byte, hash uint32) ([]byte, error) {
table := cdb.index[hash&0xff]
if table.length == 0 {
return nil, nil
}
// Probe the given hash table, starting at the given slot.
startingSlot := (hash >> 8) % table.length
slot := startingSlot
for {
slotOffset := table.offset + (8 * slot)
slotHash, offset, err := cdb.readTuple(slotOffset)
if err != nil {
return nil, err
}
// An empty slot means the key doesn't exist.
if slotHash == 0 {
break
} else if slotHash == hash {
value, err := cdb.getValueAt(offset, key)
if err != nil {
return nil, err
} else if value != nil {
return value, nil
}
}
slot = (slot + 1) % table.length
if slot == startingSlot {
break
}
}
return nil, nil
}
// Close closes the database to further reads.
func (cdb *CDB) Close() error {
var err error
if cdb.readerCloser != nil {
err = cdb.readerCloser.Close()
}
cdb.reader = nil
cdb.readerBytes = nil
cdb.readerCloser = nil
return err
}
func (cdb *CDB) readIndex() error {
buf, err := cdb.readAt(0, indexSize)
if err != nil {
return err
}
for i := 0; i < 256; i++ {
off := i * 8
cdb.index[i] = table{
offset: binary.LittleEndian.Uint32(buf[off : off+4]),
length: binary.LittleEndian.Uint32(buf[off+4 : off+8]),
}
}
return nil
}
func (cdb *CDB) getValueAt(offset uint32, expectedKey []byte) ([]byte, error) {
keyLength, valueLength, err := cdb.readTuple(offset)
if err != nil {
return nil, err
}
// We can compare key lengths before reading the key at all.
if int(keyLength) != len(expectedKey) {
return nil, nil
}
var buf []byte
buf, err = cdb.readAt(offset+8, keyLength+valueLength)
if err != nil {
return nil, err
}
// If they keys don't match, this isn't it.
if bytes.Compare(buf[:keyLength], expectedKey) != 0 {
return nil, nil
}
return buf[keyLength:], nil
}