diff --git a/database/storage/badger/badger.go b/database/storage/badger/badger.go index 2b90f77..83aa821 100644 --- a/database/storage/badger/badger.go +++ b/database/storage/badger/badger.go @@ -1 +1,146 @@ package badger + +import ( + "errors" + "time" + + "github.com/dgraph-io/badger" + + "github.com/Safing/portbase/database/iterator" + "github.com/Safing/portbase/database/model" + "github.com/Safing/portbase/database/query" + "github.com/Safing/portbase/database/storage" +) + +// Badger database made pluggable for portbase. +type Badger struct { + name string + db *badger.DB +} + +func init() { + storage.Register("badger", NewBadger) +} + +// NewBadger opens/creates a badger database. +func NewBadger(name, location string) (storage.Interface, error) { + opts := badger.DefaultOptions + opts.Dir = location + opts.ValueDir = location + + db, err := badger.Open(opts) + if err != nil { + return nil, err + } + + return &Badger{ + name: name, + db: db, + }, nil +} + +// Exists returns whether an entry with the given key exists. +func (b *Badger) Exists(key string) (bool, error) { + err := b.db.View(func(txn *badger.Txn) error { + _, err := txn.Get([]byte(key)) + if err != nil { + if err == badger.ErrKeyNotFound { + return nil + } + return err + } + return nil + }) + if err == nil { + return true, nil + } + return false, nil +} + +// Get returns a database record. +func (b *Badger) Get(key string) (model.Model, error) { + var item *badger.Item + + err := b.db.View(func(txn *badger.Txn) error { + var err error + item, err = txn.Get([]byte(key)) + if err != nil { + if err == badger.ErrKeyNotFound { + return storage.ErrNotFound + } + return err + } + return nil + }) + if err != nil { + return nil, err + } + + if item.IsDeletedOrExpired() { + return nil, storage.ErrNotFound + } + + data, err := item.ValueCopy(nil) + if err != nil { + return nil, err + } + + m, err := model.NewRawWrapper(b.name, string(item.Key()), data) + if err != nil { + return nil, err + } + return m, nil +} + +// Put stores a record in the database. +func (b *Badger) Put(m model.Model) error { + data, err := m.MarshalRecord() + if err != nil { + return err + } + + err = b.db.Update(func(txn *badger.Txn) error { + if m.Meta().GetAbsoluteExpiry() > 0 { + txn.SetWithTTL([]byte(m.DatabaseKey()), data, time.Duration(m.Meta().GetRelativeExpiry())) + } else { + txn.Set([]byte(m.DatabaseKey()), data) + } + return nil + }) + return err +} + +// Delete deletes a record from the database. +func (b *Badger) Delete(key string) error { + return b.db.Update(func(txn *badger.Txn) error { + err := txn.Delete([]byte(key)) + if err != nil && err != badger.ErrKeyNotFound { + return err + } + return nil + }) +} + +// Query returns a an iterator for the supplied query. +func (b *Badger) Query(q *query.Query) (*iterator.Iterator, error) { + return nil, errors.New("query not implemented by badger") +} + +// Maintain runs a light maintenance operation on the database. +func (b *Badger) Maintain() error { + b.db.RunValueLogGC(0.7) + return nil +} + +// MaintainThorough runs a thorough maintenance operation on the database. +func (b *Badger) MaintainThorough() (err error) { + for err == nil { + err = b.db.RunValueLogGC(0.7) + } + return nil +} + +// Shutdown shuts down the database. +func (b *Badger) Shutdown() error { + return b.db.Close() +} diff --git a/database/storage/bbolt/bbolt.go b/database/storage/bbolt/bbolt.go deleted file mode 100644 index 0c18c51..0000000 --- a/database/storage/bbolt/bbolt.go +++ /dev/null @@ -1,9 +0,0 @@ -package bbolt - -import bolt "go.etcd.io/bbolt" - -db, err := bolt.Open(path, 0666, nil) -if err != nil { - return err -} -defer db.Close() diff --git a/database/storage/errors.go b/database/storage/errors.go new file mode 100644 index 0000000..91c7689 --- /dev/null +++ b/database/storage/errors.go @@ -0,0 +1,8 @@ +package storage + +import "errors" + +// Errors for storages +var ( + ErrNotFound = errors.New("not found") +) diff --git a/database/storage/interface.go b/database/storage/interface.go index e1fa952..f6753f9 100644 --- a/database/storage/interface.go +++ b/database/storage/interface.go @@ -8,27 +8,13 @@ import ( // Interface defines the database storage API. type Interface interface { - // Retrieve Exists(key string) (bool, error) Get(key string) (model.Model, error) - - // Modify - Create(model model.Model) error - Update(model model.Model) error // create when not exists - UpdateOrCreate(model model.Model) error // update, create if not exists. + Put(m model.Model) error Delete(key string) error + Query(q *query.Query) (*iterator.Iterator, error) - // Partial - // What happens if I mutate a value that does not yet exist? How would I know its type? - InsertPartial(key string, partialObject interface{}) - InsertValue(key string, attribute string, value interface{}) - - // Query - Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) - - // Meta - SetAbsoluteExpiry(key string, time int64) - SetRelativateExpiry(key string, duration int64) - MakeCrownJewel(key string) - MakeSecret(key string) + Maintain() error + MaintainThorough() error + Shutdown() error } diff --git a/database/storage/sinkhole/sinkhole.go b/database/storage/sinkhole/sinkhole.go new file mode 100644 index 0000000..fe6ec4f --- /dev/null +++ b/database/storage/sinkhole/sinkhole.go @@ -0,0 +1,66 @@ +package sinkhole + +import ( + "errors" + + "github.com/Safing/portbase/database/iterator" + "github.com/Safing/portbase/database/model" + "github.com/Safing/portbase/database/query" + "github.com/Safing/portbase/database/storage" +) + +// Sinkhole is a dummy storage. +type Sinkhole struct { + name string +} + +func init() { + storage.Register("sinkhole", NewSinkhole) +} + +// NewSinkhole creates a dummy database. +func NewSinkhole(name, location string) (storage.Interface, error) { + return &Sinkhole{ + name: name, + }, nil +} + +// Exists returns whether an entry with the given key exists. +func (s *Sinkhole) Exists(key string) (bool, error) { + return false, nil +} + +// Get returns a database record. +func (s *Sinkhole) Get(key string) (model.Model, error) { + return nil, storage.ErrNotFound +} + +// Put stores a record in the database. +func (s *Sinkhole) Put(m model.Model) error { + return nil +} + +// Delete deletes a record from the database. +func (s *Sinkhole) Delete(key string) error { + return nil +} + +// Query returns a an iterator for the supplied query. +func (s *Sinkhole) Query(q *query.Query) (*iterator.Iterator, error) { + return nil, errors.New("query not implemented by sinkhole") +} + +// Maintain runs a light maintenance operation on the database. +func (s *Sinkhole) Maintain() error { + return nil +} + +// MaintainThorough runs a thorough maintenance operation on the database. +func (s *Sinkhole) MaintainThorough() (err error) { + return nil +} + +// Shutdown shuts down the database. +func (s *Sinkhole) Shutdown() error { + return nil +} diff --git a/database/storage/storages.go b/database/storage/storages.go new file mode 100644 index 0000000..f4a3c27 --- /dev/null +++ b/database/storage/storages.go @@ -0,0 +1,42 @@ +package storage + +import ( + "errors" + "fmt" + "sync" +) + +// A Factory creates a new database of it's type. +type Factory func(name, location string) (Interface, error) + +var ( + storages map[string]Factory + storagesLock sync.Mutex +) + +// Register registers a new storage type. +func Register(name string, factory Factory) error { + storagesLock.Lock() + defer storagesLock.Unlock() + + _, ok := storages[name] + if ok { + return errors.New("factory for this type already exists") + } + + storages[name] = factory + return nil +} + +// StartDatabase starts a new database with the given name and storageType at location. +func StartDatabase(name, storageType, location string) (Interface, error) { + storagesLock.Lock() + defer storagesLock.Unlock() + + factory, ok := storages[name] + if !ok { + return nil, fmt.Errorf("storage of this type (%s) does not exist", storageType) + } + + return factory(name, location) +}