diff --git a/database/controller.go b/database/controller.go index e015cfb..bf5de07 100644 --- a/database/controller.go +++ b/database/controller.go @@ -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 diff --git a/database/interface.go b/database/interface.go index d0a6552..d3d18fb 100644 --- a/database/interface.go +++ b/database/interface.go @@ -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) } diff --git a/database/query/README.md b/database/query/README.md index 9311417..455eb95 100644 --- a/database/query/README.md +++ b/database/query/README.md @@ -21,28 +21,28 @@ 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 | `>=` | -| LessThan | `<` | int | int64 | `<` | -| LessThanOrEqual | `<=` | int | int64 | `<=` | -| FloatEquals | `f==` | float | float64 | `==` | -| FloatGreaterThan | `f>` | float | float64 | `>` | -| FloatGreaterThanOrEqual | `f>=` | float | float64 | `>=` | -| FloatLessThan | `f<` | float | float64 | `<` | -| FloatLessThanOrEqual | `f<=` | float | float64 | `<=` | -| SameAs | `sameas`, `s==` | string | string | `==` | -| Contains | `contains`, `co` | string | string | `strings.Contains()` | -| 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()` | -| Is | `is` | bool* | bool | `==` | -| Exists | `exists`, `ex` | any | n/a | n/a | +| Name | Textual | Req. Type | Internal Type | Compared with | +|-------------------------|--------------------|-----------|---------------|---------------------------| +| Equals | `==` | int | int64 | `==` | +| GreaterThan | `>` | int | int64 | `>` | +| GreaterThanOrEqual | `>=` | int | int64 | `>=` | +| LessThan | `<` | int | int64 | `<` | +| LessThanOrEqual | `<=` | int | int64 | `<=` | +| FloatEquals | `f==` | float | float64 | `==` | +| FloatGreaterThan | `f>` | float | float64 | `>` | +| FloatGreaterThanOrEqual | `f>=` | float | float64 | `>=` | +| FloatLessThan | `f<` | float | float64 | `<` | +| FloatLessThanOrEqual | `f<=` | float | float64 | `<=` | +| SameAs | `sameas`, `s==` | string | string | `==` | +| Contains | `contains`, `co` | string | string | `strings.Contains()` | +| 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 | 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 diff --git a/database/registry.go b/database/registry.go index 81d1de4..2e27b6c 100644 --- a/database/registry.go +++ b/database/registry.go @@ -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) diff --git a/database/storage/badger/badger.go b/database/storage/badger/badger.go index dd0487c..2501f9b 100644 --- a/database/storage/badger/badger.go +++ b/database/storage/badger/badger.go @@ -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) diff --git a/database/storage/bbolt/bbolt.go b/database/storage/bbolt/bbolt.go index 214605c..48cd0d0 100644 --- a/database/storage/bbolt/bbolt.go +++ b/database/storage/bbolt/bbolt.go @@ -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) diff --git a/database/storage/fstree/fstree.go b/database/storage/fstree/fstree.go index a96f914..0cf72d3 100644 --- a/database/storage/fstree/fstree.go +++ b/database/storage/fstree/fstree.go @@ -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) diff --git a/database/storage/hashmap/map.go b/database/storage/hashmap/map.go index 87c6272..eb25f24 100644 --- a/database/storage/hashmap/map.go +++ b/database/storage/hashmap/map.go @@ -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() diff --git a/database/storage/injectbase.go b/database/storage/injectbase.go index 467c1ba..b91257f 100644 --- a/database/storage/injectbase.go +++ b/database/storage/injectbase.go @@ -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. diff --git a/database/storage/interface.go b/database/storage/interface.go index b1cfcb1..a7d111e 100644 --- a/database/storage/interface.go +++ b/database/storage/interface.go @@ -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) diff --git a/database/storage/sinkhole/sinkhole.go b/database/storage/sinkhole/sinkhole.go index d033638..af6b082 100644 --- a/database/storage/sinkhole/sinkhole.go +++ b/database/storage/sinkhole/sinkhole.go @@ -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