2021-08-31 01:09:37 -07:00
|
|
|
package memdb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
|
2022-01-11 14:29:53 -08:00
|
|
|
"github.com/cosmos/cosmos-sdk/db"
|
2021-10-05 08:39:55 -07:00
|
|
|
dbutil "github.com/cosmos/cosmos-sdk/db/internal"
|
2021-08-31 01:09:37 -07:00
|
|
|
"github.com/google/btree"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// The approximate number of items and children per B-tree node. Tuned with benchmarks.
|
|
|
|
bTreeDegree = 32
|
|
|
|
)
|
|
|
|
|
|
|
|
// MemDB is an in-memory database backend using a B-tree for storage.
|
|
|
|
//
|
|
|
|
// For performance reasons, all given and returned keys and values are pointers to the in-memory
|
|
|
|
// database, so modifying them will cause the stored values to be modified as well. All DB methods
|
|
|
|
// already specify that keys and values should be considered read-only, but this is especially
|
|
|
|
// important with MemDB.
|
|
|
|
//
|
|
|
|
// Versioning is implemented by maintaining references to copy-on-write clones of the backing btree.
|
|
|
|
//
|
2021-12-16 05:09:57 -08:00
|
|
|
// Note: Currently, transactions do not detect write conflicts, so multiple writers cannot be
|
|
|
|
// safely committed to overlapping domains. Because of this, the number of open writers is
|
|
|
|
// limited to 1.
|
2021-08-31 01:09:37 -07:00
|
|
|
type MemDB struct {
|
|
|
|
btree *btree.BTree // Main contents
|
|
|
|
mtx sync.RWMutex // Guards version history
|
|
|
|
saved map[uint64]*btree.BTree // Past versions
|
2022-01-11 14:29:53 -08:00
|
|
|
vmgr *db.VersionManager // Mirrors version keys
|
2021-08-31 01:09:37 -07:00
|
|
|
openWriters int32 // Open writers
|
|
|
|
}
|
|
|
|
|
|
|
|
type dbTxn struct {
|
|
|
|
btree *btree.BTree
|
|
|
|
db *MemDB
|
|
|
|
}
|
|
|
|
type dbWriter struct{ dbTxn }
|
|
|
|
|
|
|
|
var (
|
2022-01-11 14:29:53 -08:00
|
|
|
_ db.DBConnection = (*MemDB)(nil)
|
|
|
|
_ db.DBReader = (*dbTxn)(nil)
|
|
|
|
_ db.DBWriter = (*dbWriter)(nil)
|
|
|
|
_ db.DBReadWriter = (*dbWriter)(nil)
|
2021-08-31 01:09:37 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
// item is a btree.Item with byte slices as keys and values
|
|
|
|
type item struct {
|
|
|
|
key []byte
|
|
|
|
value []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewDB creates a new in-memory database.
|
|
|
|
func NewDB() *MemDB {
|
|
|
|
return &MemDB{
|
|
|
|
btree: btree.New(bTreeDegree),
|
|
|
|
saved: make(map[uint64]*btree.BTree),
|
2022-01-11 14:29:53 -08:00
|
|
|
vmgr: db.NewVersionManager(nil),
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) newTxn(tree *btree.BTree) dbTxn {
|
|
|
|
return dbTxn{tree, dbm}
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close implements DB.
|
|
|
|
// Close is a noop since for an in-memory database, we don't have a destination to flush
|
|
|
|
// contents to nor do we want any data loss on invoking Close().
|
|
|
|
// See the discussion in https://github.com/tendermint/tendermint/libs/pull/56
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) Close() error {
|
2021-08-31 01:09:37 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Versions implements DBConnection.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) Versions() (db.VersionSet, error) {
|
|
|
|
dbm.mtx.RLock()
|
|
|
|
defer dbm.mtx.RUnlock()
|
|
|
|
return dbm.vmgr, nil
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reader implements DBConnection.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) Reader() db.DBReader {
|
|
|
|
dbm.mtx.RLock()
|
|
|
|
defer dbm.mtx.RUnlock()
|
|
|
|
ret := dbm.newTxn(dbm.btree)
|
2021-08-31 01:09:37 -07:00
|
|
|
return &ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReaderAt implements DBConnection.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) ReaderAt(version uint64) (db.DBReader, error) {
|
|
|
|
dbm.mtx.RLock()
|
|
|
|
defer dbm.mtx.RUnlock()
|
|
|
|
tree, ok := dbm.saved[version]
|
2021-08-31 01:09:37 -07:00
|
|
|
if !ok {
|
2022-01-11 14:29:53 -08:00
|
|
|
return nil, db.ErrVersionDoesNotExist
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
2022-01-11 14:29:53 -08:00
|
|
|
ret := dbm.newTxn(tree)
|
2021-08-31 01:09:37 -07:00
|
|
|
return &ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Writer implements DBConnection.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) Writer() db.DBWriter {
|
|
|
|
return dbm.ReadWriter()
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReadWriter implements DBConnection.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) ReadWriter() db.DBReadWriter {
|
|
|
|
dbm.mtx.RLock()
|
|
|
|
defer dbm.mtx.RUnlock()
|
|
|
|
atomic.AddInt32(&dbm.openWriters, 1)
|
2021-08-31 01:09:37 -07:00
|
|
|
// Clone creates a copy-on-write extension of the current tree
|
2022-01-11 14:29:53 -08:00
|
|
|
return &dbWriter{dbm.newTxn(dbm.btree.Clone())}
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) save(target uint64) (uint64, error) {
|
|
|
|
dbm.mtx.Lock()
|
|
|
|
defer dbm.mtx.Unlock()
|
|
|
|
if dbm.openWriters > 0 {
|
|
|
|
return 0, db.ErrOpenTransactions
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
|
2022-01-11 14:29:53 -08:00
|
|
|
newVmgr := dbm.vmgr.Copy()
|
2021-08-31 01:09:37 -07:00
|
|
|
target, err := newVmgr.Save(target)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2022-01-11 14:29:53 -08:00
|
|
|
dbm.saved[target] = dbm.btree
|
|
|
|
dbm.vmgr = newVmgr
|
2021-08-31 01:09:37 -07:00
|
|
|
return target, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SaveVersion implements DBConnection.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) SaveNextVersion() (uint64, error) {
|
|
|
|
return dbm.save(0)
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// SaveNextVersion implements DBConnection.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) SaveVersion(target uint64) error {
|
2021-08-31 01:09:37 -07:00
|
|
|
if target == 0 {
|
2022-01-11 14:29:53 -08:00
|
|
|
return db.ErrInvalidVersion
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
2022-01-11 14:29:53 -08:00
|
|
|
_, err := dbm.save(target)
|
2021-08-31 01:09:37 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteVersion implements DBConnection.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) DeleteVersion(target uint64) error {
|
|
|
|
dbm.mtx.Lock()
|
|
|
|
defer dbm.mtx.Unlock()
|
|
|
|
if _, has := dbm.saved[target]; !has {
|
|
|
|
return db.ErrVersionDoesNotExist
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
2022-01-11 14:29:53 -08:00
|
|
|
delete(dbm.saved, target)
|
|
|
|
dbm.vmgr = dbm.vmgr.Copy()
|
|
|
|
dbm.vmgr.Delete(target)
|
2021-08-31 01:09:37 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) Revert() error {
|
|
|
|
dbm.mtx.RLock()
|
|
|
|
defer dbm.mtx.RUnlock()
|
|
|
|
if dbm.openWriters > 0 {
|
|
|
|
return db.ErrOpenTransactions
|
2021-10-08 03:44:37 -07:00
|
|
|
}
|
|
|
|
|
2022-01-11 14:29:53 -08:00
|
|
|
last := dbm.vmgr.Last()
|
2021-10-08 03:44:37 -07:00
|
|
|
if last == 0 {
|
2022-01-11 14:29:53 -08:00
|
|
|
dbm.btree = btree.New(bTreeDegree)
|
2021-10-08 03:44:37 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var has bool
|
2022-01-11 14:29:53 -08:00
|
|
|
dbm.btree, has = dbm.saved[last]
|
2021-10-08 03:44:37 -07:00
|
|
|
if !has {
|
|
|
|
return fmt.Errorf("bad version history: version %v not saved", last)
|
|
|
|
}
|
2022-01-11 14:29:53 -08:00
|
|
|
for ver, _ := range dbm.saved {
|
2021-10-08 03:44:37 -07:00
|
|
|
if ver > last {
|
2022-01-11 14:29:53 -08:00
|
|
|
delete(dbm.saved, ver)
|
2021-10-08 03:44:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-31 01:09:37 -07:00
|
|
|
// Get implements DBReader.
|
|
|
|
func (tx *dbTxn) Get(key []byte) ([]byte, error) {
|
2021-10-05 08:39:55 -07:00
|
|
|
if tx.btree == nil {
|
2022-01-11 14:29:53 -08:00
|
|
|
return nil, db.ErrTransactionClosed
|
2021-10-05 08:39:55 -07:00
|
|
|
}
|
2021-08-31 01:09:37 -07:00
|
|
|
if len(key) == 0 {
|
2022-01-11 14:29:53 -08:00
|
|
|
return nil, db.ErrKeyEmpty
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
i := tx.btree.Get(newKey(key))
|
|
|
|
if i != nil {
|
|
|
|
return i.(*item).value, nil
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Has implements DBReader.
|
|
|
|
func (tx *dbTxn) Has(key []byte) (bool, error) {
|
2021-10-05 08:39:55 -07:00
|
|
|
if tx.btree == nil {
|
2022-01-11 14:29:53 -08:00
|
|
|
return false, db.ErrTransactionClosed
|
2021-10-05 08:39:55 -07:00
|
|
|
}
|
2021-08-31 01:09:37 -07:00
|
|
|
if len(key) == 0 {
|
2022-01-11 14:29:53 -08:00
|
|
|
return false, db.ErrKeyEmpty
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
return tx.btree.Has(newKey(key)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set implements DBWriter.
|
|
|
|
func (tx *dbWriter) Set(key []byte, value []byte) error {
|
2021-10-05 08:39:55 -07:00
|
|
|
if tx.btree == nil {
|
2022-01-11 14:29:53 -08:00
|
|
|
return db.ErrTransactionClosed
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
2021-10-05 08:39:55 -07:00
|
|
|
if err := dbutil.ValidateKv(key, value); err != nil {
|
|
|
|
return err
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
tx.btree.ReplaceOrInsert(newPair(key, value))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete implements DBWriter.
|
|
|
|
func (tx *dbWriter) Delete(key []byte) error {
|
2021-10-05 08:39:55 -07:00
|
|
|
if tx.btree == nil {
|
2022-01-11 14:29:53 -08:00
|
|
|
return db.ErrTransactionClosed
|
2021-10-05 08:39:55 -07:00
|
|
|
}
|
2021-08-31 01:09:37 -07:00
|
|
|
if len(key) == 0 {
|
2022-01-11 14:29:53 -08:00
|
|
|
return db.ErrKeyEmpty
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
tx.btree.Delete(newKey(key))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterator implements DBReader.
|
|
|
|
// Takes out a read-lock on the database until the iterator is closed.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (tx *dbTxn) Iterator(start, end []byte) (db.Iterator, error) {
|
2021-10-05 08:39:55 -07:00
|
|
|
if tx.btree == nil {
|
2022-01-11 14:29:53 -08:00
|
|
|
return nil, db.ErrTransactionClosed
|
2021-10-05 08:39:55 -07:00
|
|
|
}
|
2021-08-31 01:09:37 -07:00
|
|
|
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
|
2022-01-11 14:29:53 -08:00
|
|
|
return nil, db.ErrKeyEmpty
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
return newMemDBIterator(tx, start, end, false), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReverseIterator implements DBReader.
|
|
|
|
// Takes out a read-lock on the database until the iterator is closed.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (tx *dbTxn) ReverseIterator(start, end []byte) (db.Iterator, error) {
|
2021-10-05 08:39:55 -07:00
|
|
|
if tx.btree == nil {
|
2022-01-11 14:29:53 -08:00
|
|
|
return nil, db.ErrTransactionClosed
|
2021-10-05 08:39:55 -07:00
|
|
|
}
|
2021-08-31 01:09:37 -07:00
|
|
|
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
|
2022-01-11 14:29:53 -08:00
|
|
|
return nil, db.ErrKeyEmpty
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
return newMemDBIterator(tx, start, end, true), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Commit implements DBWriter.
|
|
|
|
func (tx *dbWriter) Commit() error {
|
2021-10-05 08:39:55 -07:00
|
|
|
if tx.btree == nil {
|
2022-01-11 14:29:53 -08:00
|
|
|
return db.ErrTransactionClosed
|
2021-10-05 08:39:55 -07:00
|
|
|
}
|
2021-08-31 01:09:37 -07:00
|
|
|
tx.db.mtx.Lock()
|
|
|
|
defer tx.db.mtx.Unlock()
|
|
|
|
tx.db.btree = tx.btree
|
2021-10-05 08:39:55 -07:00
|
|
|
return tx.Discard()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Discard implements DBReader.
|
|
|
|
func (tx *dbTxn) Discard() error {
|
|
|
|
if tx.btree != nil {
|
|
|
|
tx.btree = nil
|
|
|
|
}
|
2021-08-31 01:09:37 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-05 08:39:55 -07:00
|
|
|
// Discard implements DBWriter.
|
|
|
|
func (tx *dbWriter) Discard() error {
|
|
|
|
if tx.btree != nil {
|
|
|
|
defer atomic.AddInt32(&tx.db.openWriters, -1)
|
|
|
|
}
|
|
|
|
return tx.dbTxn.Discard()
|
2021-08-31 01:09:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Print prints the database contents.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) Print() error {
|
|
|
|
dbm.mtx.RLock()
|
|
|
|
defer dbm.mtx.RUnlock()
|
2021-08-31 01:09:37 -07:00
|
|
|
|
2022-01-11 14:29:53 -08:00
|
|
|
dbm.btree.Ascend(func(i btree.Item) bool {
|
2021-08-31 01:09:37 -07:00
|
|
|
item := i.(*item)
|
|
|
|
fmt.Printf("[%X]:\t[%X]\n", item.key, item.value)
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stats implements DBConnection.
|
2022-01-11 14:29:53 -08:00
|
|
|
func (dbm *MemDB) Stats() map[string]string {
|
|
|
|
dbm.mtx.RLock()
|
|
|
|
defer dbm.mtx.RUnlock()
|
2021-08-31 01:09:37 -07:00
|
|
|
|
|
|
|
stats := make(map[string]string)
|
|
|
|
stats["database.type"] = "memDB"
|
2022-01-11 14:29:53 -08:00
|
|
|
stats["database.size"] = fmt.Sprintf("%d", dbm.btree.Len())
|
2021-08-31 01:09:37 -07:00
|
|
|
return stats
|
|
|
|
}
|
|
|
|
|
|
|
|
// Less implements btree.Item.
|
|
|
|
func (i *item) Less(other btree.Item) bool {
|
|
|
|
// this considers nil == []byte{}, but that's ok since we handle nil endpoints
|
|
|
|
// in iterators specially anyway
|
|
|
|
return bytes.Compare(i.key, other.(*item).key) == -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// newKey creates a new key item.
|
|
|
|
func newKey(key []byte) *item {
|
|
|
|
return &item{key: key}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newPair creates a new pair item.
|
|
|
|
func newPair(key, value []byte) *item {
|
|
|
|
return &item{key: key, value: value}
|
|
|
|
}
|