monorepo/native/desktop/maplefile/pkg/storage/leveldb/leveldb.go

218 lines
5.7 KiB
Go

package leveldb
import (
"log"
"strings"
"github.com/syndtr/goleveldb/leveldb"
dberr "github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/opt"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/pkg/storage"
)
// storageImpl implements the db.Database interface.
// It uses a LevelDB database to store key-value pairs.
type storageImpl struct {
// The LevelDB database instance.
db *leveldb.DB
transaction *leveldb.Transaction
}
// NewDiskStorage creates a new instance of the storageImpl.
// It opens the database file at the specified path and returns an error if it fails.
func NewDiskStorage(provider LevelDBConfigurationProvider, logger *zap.Logger) storage.Storage {
logger = logger.Named("leveldb")
if provider == nil {
log.Fatal("NewDiskStorage: missing LevelDB configuration provider\n")
}
if provider.GetDBPath() == "" {
log.Fatal("NewDiskStorage: cannot have empty filepath for the database\n")
}
if provider.GetDBName() == "" {
log.Fatal("NewDiskStorage: cannot have empty db name for the database\n")
}
o := &opt.Options{
Filter: filter.NewBloomFilter(10),
}
filePath := provider.GetDBPath() + "/" + provider.GetDBName()
db, err := leveldb.OpenFile(filePath, o)
if err != nil {
log.Fatalf("NewDiskStorage: failed loading up key value storer adapter at %v with error: %v\n", filePath, err)
}
return &storageImpl{
db: db,
}
}
// Get retrieves a value from the database by its key.
// It returns an error if the key is not found.
func (impl *storageImpl) Get(k string) ([]byte, error) {
if impl.transaction == nil {
bin, err := impl.db.Get([]byte(k), nil)
if err == dberr.ErrNotFound {
return nil, nil
}
return bin, nil
}
bin, err := impl.transaction.Get([]byte(k), nil)
if err == dberr.ErrNotFound {
return nil, nil
}
return bin, nil
}
// Set sets a value in the database by its key.
// It returns an error if the operation fails.
func (impl *storageImpl) Set(k string, val []byte) error {
if impl.transaction == nil {
impl.db.Delete([]byte(k), nil)
err := impl.db.Put([]byte(k), val, nil)
if err == dberr.ErrNotFound {
return nil
}
return err
}
impl.transaction.Delete([]byte(k), nil)
err := impl.transaction.Put([]byte(k), val, nil)
if err == dberr.ErrNotFound {
return nil
}
return err
}
// Delete deletes a value from the database by its key.
// It returns an error if the operation fails.
func (impl *storageImpl) Delete(k string) error {
if impl.transaction == nil {
err := impl.db.Delete([]byte(k), nil)
if err == dberr.ErrNotFound {
return nil
}
return err
}
err := impl.transaction.Delete([]byte(k), nil)
if err == dberr.ErrNotFound {
return nil
}
return err
}
// Iterate iterates over the key-value pairs in the database, starting from the specified key prefix.
// It calls the provided function for each pair.
// It returns an error if the iteration fails.
func (impl *storageImpl) Iterate(processFunc func(key, value []byte) error) error {
if impl.transaction == nil {
iter := impl.db.NewIterator(nil, nil)
for ok := iter.First(); ok; ok = iter.Next() {
// Call the passed function for each key-value pair.
err := processFunc(iter.Key(), iter.Value())
if err == dberr.ErrNotFound {
return nil
}
if err != nil {
return err // Exit early if the processing function returns an error.
}
}
iter.Release()
return iter.Error()
}
iter := impl.transaction.NewIterator(nil, nil)
for ok := iter.First(); ok; ok = iter.Next() {
// Call the passed function for each key-value pair.
err := processFunc(iter.Key(), iter.Value())
if err == dberr.ErrNotFound {
return nil
}
if err != nil {
return err // Exit early if the processing function returns an error.
}
}
iter.Release()
return iter.Error()
}
func (impl *storageImpl) IterateWithFilterByKeys(ks []string, processFunc func(key, value []byte) error) error {
if impl.transaction == nil {
iter := impl.db.NewIterator(nil, nil)
for ok := iter.First(); ok; ok = iter.Next() {
// Iterate over our keys to search by.
for _, k := range ks {
searchKey := strings.ToLower(k)
targetKey := strings.ToLower(string(iter.Key()))
// If the item we currently have matches our keys then execute.
if searchKey == targetKey {
// Call the passed function for each key-value pair.
err := processFunc(iter.Key(), iter.Value())
if err == dberr.ErrNotFound {
return nil
}
if err != nil {
return err // Exit early if the processing function returns an error.
}
}
}
}
iter.Release()
return iter.Error()
}
iter := impl.transaction.NewIterator(nil, nil)
for ok := iter.First(); ok; ok = iter.Next() {
// Call the passed function for each key-value pair.
err := processFunc(iter.Key(), iter.Value())
if err == dberr.ErrNotFound {
return nil
}
if err != nil {
return err // Exit early if the processing function returns an error.
}
}
iter.Release()
return iter.Error()
}
// Close closes the database.
// It returns an error if the operation fails.
func (impl *storageImpl) Close() error {
if impl.transaction != nil {
impl.transaction.Discard()
}
return impl.db.Close()
}
func (impl *storageImpl) OpenTransaction() error {
transaction, err := impl.db.OpenTransaction()
if err != nil {
return nil
}
impl.transaction = transaction
return nil
}
func (impl *storageImpl) CommitTransaction() error {
defer func() {
impl.transaction = nil
}()
// Commit the snapshot to the database
return impl.transaction.Commit()
}
func (impl *storageImpl) DiscardTransaction() {
defer func() {
impl.transaction = nil
}()
impl.transaction.Discard()
}