Add GetMeta to database storage interface

This commit is contained in:
Daniel 2021-05-11 14:57:53 +02:00
parent 3f3786b854
commit 0061572e1b
11 changed files with 170 additions and 32 deletions

View file

@ -42,7 +42,7 @@ func (c *Controller) Injected() bool {
return c.storage.Injected()
}
// Get return the record with the given key.
// Get returns the record with the given key.
func (c *Controller) Get(key string) (record.Record, error) {
if shuttingDown.IsSet() {
return nil, ErrShuttingDown
@ -76,6 +76,28 @@ func (c *Controller) Get(key string) (record.Record, error) {
return r, nil
}
// Get returns the metadata of the record with the given key.
func (c *Controller) GetMeta(key string) (*record.Meta, error) {
if shuttingDown.IsSet() {
return nil, ErrShuttingDown
}
m, err := c.storage.GetMeta(key)
if err != nil {
// replace not found error
if err == storage.ErrNotFound {
return nil, ErrNotFound
}
return nil, err
}
if !m.CheckValidity() {
return nil, ErrNotFound
}
return m, nil
}
// Put saves a record in the database, executes any registered
// pre-put hooks and finally send an update to all subscribers.
// The record must be locked and secured from concurrent access

View file

@ -201,6 +201,40 @@ func (i *Interface) getRecord(dbName string, dbKey string, mustBeWriteable bool)
return r, db, nil
}
func (i *Interface) getMeta(dbName string, dbKey string, mustBeWriteable bool) (m *record.Meta, db *Controller, err error) {
if dbName == "" {
dbName, dbKey = record.ParseKey(dbKey)
}
db, err = getController(dbName)
if err != nil {
return nil, nil, err
}
if mustBeWriteable && db.ReadOnly() {
return nil, db, ErrReadOnly
}
r := i.checkCache(dbName + ":" + dbKey)
if r != nil {
if !i.options.hasAccessPermission(r) {
return nil, db, ErrPermissionDenied
}
return r.Meta(), db, nil
}
m, err = db.GetMeta(dbKey)
if err != nil {
return nil, db, err
}
if !m.CheckPermission(i.options.Local, i.options.Internal) {
return nil, db, ErrPermissionDenied
}
return m, db, nil
}
// InsertValue inserts a value into a record.
func (i *Interface) InsertValue(key string, attribute string, value interface{}) error {
r, db, err := i.getRecord(getDBFromKey, key, true)
@ -236,7 +270,7 @@ func (i *Interface) Put(r record.Record) (err error) {
// get record or only database
var db *Controller
if !i.options.HasAllPermissions() {
_, db, err = i.getRecord(r.DatabaseName(), r.DatabaseKey(), true)
_, db, err = i.getMeta(r.DatabaseName(), r.DatabaseKey(), true)
if err != nil && err != ErrNotFound {
return err
}
@ -247,7 +281,7 @@ func (i *Interface) Put(r record.Record) (err error) {
}
}
// Check if database is read only before we add to the cache.
// Check if database is read only.
if db.ReadOnly() {
return ErrReadOnly
}
@ -274,7 +308,7 @@ func (i *Interface) PutNew(r record.Record) (err error) {
// get record or only database
var db *Controller
if !i.options.HasAllPermissions() {
_, db, err = i.getRecord(r.DatabaseName(), r.DatabaseKey(), true)
_, db, err = i.getMeta(r.DatabaseName(), r.DatabaseKey(), true)
if err != nil && err != ErrNotFound {
return err
}
@ -285,6 +319,11 @@ func (i *Interface) PutNew(r record.Record) (err error) {
}
}
// Check if database is read only.
if db.ReadOnly() {
return ErrReadOnly
}
r.Lock()
if r.Meta() != nil {
r.Meta().Reset()
@ -328,6 +367,13 @@ func (i *Interface) PutMany(dbName string) (put func(record.Record) error) {
}
}
// Check if database is read only.
if db.ReadOnly() {
return func(r record.Record) error {
return ErrReadOnly
}
}
// start database access
dbBatch, errs := db.PutMany()
finished := abool.New()
@ -462,6 +508,11 @@ func (i *Interface) Delete(key string) error {
return err
}
// Check if database is read only.
if db.ReadOnly() {
return ErrReadOnly
}
i.options.Apply(r)
r.Meta().Delete()
return db.Put(r)
@ -495,6 +546,11 @@ func (i *Interface) Purge(ctx context.Context, q *query.Query) (int, error) {
return 0, err
}
// Check if database is read only before we add to the cache.
if db.ReadOnly() {
return 0, ErrReadOnly
}
return db.Purge(ctx, q, i.options.Local, i.options.Internal)
}

View file

@ -22,7 +22,7 @@ Please note that some feeders may have other special characters. It is advised t
## Operators
| Name | Textual | Req. Type | Internal Type | Compared with |
|---|---|---|---|
|-------------------------|--------------------|-----------|---------------|---------------------------|
| Equals | `==` | int | int64 | `==` |
| GreaterThan | `>` | int | int64 | `>` |
| GreaterThanOrEqual | `>=` | int | int64 | `>=` |
@ -38,11 +38,11 @@ Please note that some feeders may have other special characters. It is advised t
| StartsWith | `startswith`, `sw` | string | string | `strings.HasPrefix()` |
| EndsWith | `endswith`, `ew` | string | string | `strings.HasSuffix()` |
| In | `in` | string | string | for loop with `==` |
| Matches | `matches`, `re` | string | int64 | `regexp.Regexp.Matches()` |
| Matches | `matches`, `re` | string | string | `regexp.Regexp.Matches()` |
| Is | `is` | bool* | bool | `==` |
| Exists | `exists`, `ex` | any | n/a | n/a |
\*accepts strings: 1, t, T, TRUE, true, True, 0, f, F, FALSE
\*accepts strings: 1, t, T, true, True, TRUE, 0, f, F, false, False, FALSE
## Escaping

View file

@ -25,7 +25,7 @@ var (
registry = make(map[string]*Database)
registryLock sync.Mutex
nameConstraint = regexp.MustCompile("^[A-Za-z0-9_-]{4,}$")
nameConstraint = regexp.MustCompile("^[A-Za-z0-9_-]{3,}$")
)
// Register registers a new database.
@ -56,7 +56,7 @@ func Register(new *Database) (*Database, error) {
} else {
// register new database
if !nameConstraint.MatchString(new.Name) {
return nil, errors.New("database name must only contain alphanumeric and `_-` characters and must be at least 4 characters long")
return nil, errors.New("database name must only contain alphanumeric and `_-` characters and must be at least 3 characters long")
}
now := time.Now().Round(time.Second)

View file

@ -82,6 +82,18 @@ func (b *Badger) Get(key string) (record.Record, error) {
return m, nil
}
// GetMeta returns the metadata of a database record.
func (b *Badger) GetMeta(key string) (*record.Meta, error) {
// TODO: Replace with more performant variant.
r, err := b.Get(key)
if err != nil {
return nil, err
}
return r.Meta(), nil
}
// Put stores a record in the database.
func (b *Badger) Put(r record.Record) (record.Record, error) {
data, err := r.MarshalRecord(r)

View file

@ -96,6 +96,18 @@ func (b *BBolt) Get(key string) (record.Record, error) {
return r, nil
}
// GetMeta returns the metadata of a database record.
func (b *BBolt) GetMeta(key string) (*record.Meta, error) {
// TODO: Replace with more performant variant.
r, err := b.Get(key)
if err != nil {
return nil, err
}
return r.Meta(), nil
}
// Put stores a record in the database.
func (b *BBolt) Put(r record.Record) (record.Record, error) {
data, err := r.MarshalRecord(r)

View file

@ -104,6 +104,18 @@ func (fst *FSTree) Get(key string) (record.Record, error) {
return r, nil
}
// GetMeta returns the metadata of a database record.
func (fst *FSTree) GetMeta(key string) (*record.Meta, error) {
// TODO: Replace with more performant variant.
r, err := fst.Get(key)
if err != nil {
return nil, err
}
return r.Meta(), nil
}
// Put stores a record in the database.
func (fst *FSTree) Put(r record.Record) (record.Record, error) {
dstPath, err := fst.buildFilePath(r.DatabaseKey(), true)

View file

@ -44,6 +44,18 @@ func (hm *HashMap) Get(key string) (record.Record, error) {
return r, nil
}
// GetMeta returns the metadata of a database record.
func (hm *HashMap) GetMeta(key string) (*record.Meta, error) {
// TODO: Replace with more performant variant.
r, err := hm.Get(key)
if err != nil {
return nil, err
}
return r.Meta(), nil
}
// Put stores a record in the database.
func (hm *HashMap) Put(r record.Record) (record.Record, error) {
hm.dbLock.Lock()

View file

@ -11,7 +11,8 @@ import (
)
var (
errNotImplemented = errors.New("not implemented")
// ErrNotImplemented is returned when a function is not implemented by a storage.
ErrNotImplemented = errors.New("not implemented")
)
// InjectBase is a dummy base structure to reduce boilerplate code for injected storage interfaces.
@ -22,22 +23,27 @@ var _ Interface = &InjectBase{}
// Get returns a database record.
func (i *InjectBase) Get(key string) (record.Record, error) {
return nil, errNotImplemented
return nil, ErrNotImplemented
}
// Get returns a database record.
func (i *InjectBase) GetMeta(key string) (*record.Meta, error) {
return nil, ErrNotImplemented
}
// Put stores a record in the database.
func (i *InjectBase) Put(m record.Record) (record.Record, error) {
return nil, errNotImplemented
return nil, ErrNotImplemented
}
// Delete deletes a record from the database.
func (i *InjectBase) Delete(key string) error {
return errNotImplemented
return ErrNotImplemented
}
// Query returns a an iterator for the supplied query.
func (i *InjectBase) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) {
return nil, errNotImplemented
return nil, ErrNotImplemented
}
// ReadOnly returns whether the database is read only.

View file

@ -13,6 +13,7 @@ import (
type Interface interface {
// Primary Interface
Get(key string) (record.Record, error)
GetMeta(key string) (*record.Meta, error)
Put(m record.Record) (record.Record, error)
Delete(key string) error
Query(q *query.Query, local, internal bool) (*iterator.Iterator, error)

View file

@ -44,6 +44,11 @@ func (s *Sinkhole) Get(key string) (record.Record, error) {
return nil, storage.ErrNotFound
}
// GetMeta returns the metadata of a database record.
func (s *Sinkhole) GetMeta(key string) (*record.Meta, error) {
return nil, storage.ErrNotFound
}
// Put stores a record in the database.
func (s *Sinkhole) Put(r record.Record) (record.Record, error) {
return r, nil