status-go/db/db.go

164 lines
4.5 KiB
Go

package db
import (
"path/filepath"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/syndtr/goleveldb/leveldb/storage"
"github.com/syndtr/goleveldb/leveldb/util"
"go.uber.org/zap"
"github.com/status-im/status-go/logutils"
)
type storagePrefix byte
const (
// PeersCache is used for the db entries used for peers DB
PeersCache storagePrefix = iota
// DeduplicatorCache is used for the db entries used for messages
// deduplication cache
DeduplicatorCache
// MailserversCache is a list of mail servers provided by users.
MailserversCache
// TopicHistoryBucket isolated bucket for storing history metadata.
TopicHistoryBucket
// HistoryRequestBucket isolated bucket for storing list of pending requests.
HistoryRequestBucket
)
// NewMemoryDB returns leveldb with memory backend prefixed with a bucket.
func NewMemoryDB() (*leveldb.DB, error) {
return leveldb.Open(storage.NewMemStorage(), nil)
}
// NewDBNamespace returns instance that ensures isolated operations.
func NewDBNamespace(db Storage, prefix storagePrefix) LevelDBNamespace {
return LevelDBNamespace{
db: db,
prefix: prefix,
}
}
// NewMemoryDBNamespace wraps in memory leveldb with provided bucket.
// Mostly used for tests. Including tests in other packages.
func NewMemoryDBNamespace(prefix storagePrefix) (pdb LevelDBNamespace, err error) {
db, err := NewMemoryDB()
if err != nil {
return pdb, err
}
return NewDBNamespace(LevelDBStorage{db: db}, prefix), nil
}
// Key creates a DB key for a specified service with specified data
func Key(prefix storagePrefix, data ...[]byte) []byte {
keyLength := 1
for _, d := range data {
keyLength += len(d)
}
key := make([]byte, keyLength)
key[0] = byte(prefix)
startPos := 1
for _, d := range data {
copy(key[startPos:], d[:])
startPos += len(d)
}
return key
}
// Create returns status pointer to leveldb.DB.
func Create(path, dbName string) (*leveldb.DB, error) {
// Create euphemeral storage if the node config path isn't provided
if path == "" {
return leveldb.Open(storage.NewMemStorage(), nil)
}
path = filepath.Join(path, dbName)
return Open(path, &opt.Options{OpenFilesCacheCapacity: 5})
}
// Open opens an existing leveldb database
func Open(path string, opts *opt.Options) (db *leveldb.DB, err error) {
db, err = leveldb.OpenFile(path, opts)
if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
logutils.ZapLogger().Info("database is corrupted trying to recover", zap.String("path", path))
db, err = leveldb.RecoverFile(path, nil)
}
return
}
// LevelDBNamespace database where all operations will be prefixed with a certain bucket.
type LevelDBNamespace struct {
db Storage
prefix storagePrefix
}
func (db LevelDBNamespace) prefixedKey(key []byte) []byte {
endkey := make([]byte, len(key)+1)
endkey[0] = byte(db.prefix)
copy(endkey[1:], key)
return endkey
}
func (db LevelDBNamespace) Put(key, value []byte) error {
return db.db.Put(db.prefixedKey(key), value)
}
func (db LevelDBNamespace) Get(key []byte) ([]byte, error) {
return db.db.Get(db.prefixedKey(key))
}
// Range returns leveldb util.Range prefixed with a single byte.
// If prefix is nil range will iterate over all records in a given bucket.
func (db LevelDBNamespace) Range(prefix, limit []byte) *util.Range {
if limit == nil {
return util.BytesPrefix(db.prefixedKey(prefix))
}
return &util.Range{Start: db.prefixedKey(prefix), Limit: db.prefixedKey(limit)}
}
// Delete removes key from database.
func (db LevelDBNamespace) Delete(key []byte) error {
return db.db.Delete(db.prefixedKey(key))
}
// NewIterator returns iterator for a given slice.
func (db LevelDBNamespace) NewIterator(slice *util.Range) NamespaceIterator {
return NamespaceIterator{db.db.NewIterator(slice)}
}
// NamespaceIterator wraps leveldb iterator, works mostly the same way.
// The only difference is that first byte of the key is dropped.
type NamespaceIterator struct {
iter iterator.Iterator
}
// Key returns key of the current item.
func (iter NamespaceIterator) Key() []byte {
return iter.iter.Key()[1:]
}
// Value returns actual value of the current item.
func (iter NamespaceIterator) Value() []byte {
return iter.iter.Value()
}
// Error returns accumulated error.
func (iter NamespaceIterator) Error() error {
return iter.iter.Error()
}
// Prev moves cursor backward.
func (iter NamespaceIterator) Prev() bool {
return iter.iter.Prev()
}
// Next moves cursor forward.
func (iter NamespaceIterator) Next() bool {
return iter.iter.Next()
}