2020-03-19 18:19:07 +00:00
|
|
|
// Package cache implements a faulting style read cache on top of multiple micro stores
|
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/micro/go-micro/v2/store"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type cache struct {
|
|
|
|
stores []store.Store
|
|
|
|
options store.Options
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewCache returns a new store using the underlying stores, which must be already Init()ialised
|
|
|
|
func NewCache(stores ...store.Store) store.Store {
|
|
|
|
c := &cache{}
|
|
|
|
c.stores = make([]store.Store, len(stores))
|
|
|
|
for i, s := range stores {
|
|
|
|
c.stores[i] = s
|
|
|
|
}
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2020-04-08 09:51:10 +01:00
|
|
|
func (c *cache) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-19 18:19:07 +00:00
|
|
|
func (c *cache) Init(...store.Option) error {
|
|
|
|
if len(c.stores) < 2 {
|
|
|
|
return errors.New("cache requires at least 2 stores")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) Options() store.Options {
|
|
|
|
return c.options
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) String() string {
|
|
|
|
stores := make([]string, len(c.stores))
|
|
|
|
for i, s := range c.stores {
|
|
|
|
stores[i] = s.String()
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("cache %v", stores)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) {
|
|
|
|
readOpts := store.ReadOptions{}
|
|
|
|
for _, o := range opts {
|
|
|
|
o(&readOpts)
|
|
|
|
}
|
|
|
|
|
|
|
|
if readOpts.Prefix || readOpts.Suffix {
|
|
|
|
// List, then try cached gets for each key
|
|
|
|
var lOpts []store.ListOption
|
|
|
|
if readOpts.Prefix {
|
|
|
|
lOpts = append(lOpts, store.ListPrefix(key))
|
|
|
|
}
|
|
|
|
if readOpts.Suffix {
|
|
|
|
lOpts = append(lOpts, store.ListSuffix(key))
|
|
|
|
}
|
|
|
|
if readOpts.Limit > 0 {
|
|
|
|
lOpts = append(lOpts, store.ListLimit(readOpts.Limit))
|
|
|
|
}
|
|
|
|
if readOpts.Offset > 0 {
|
|
|
|
lOpts = append(lOpts, store.ListOffset(readOpts.Offset))
|
|
|
|
}
|
|
|
|
keys, err := c.List(lOpts...)
|
|
|
|
if err != nil {
|
|
|
|
return []*store.Record{}, errors.Wrap(err, "cache.List failed")
|
|
|
|
}
|
|
|
|
recs := make([]*store.Record, len(keys))
|
|
|
|
for i, k := range keys {
|
|
|
|
r, err := c.readOne(k, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return recs, errors.Wrap(err, "cache.readOne failed")
|
|
|
|
}
|
|
|
|
recs[i] = r
|
|
|
|
}
|
|
|
|
return recs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise just try cached get
|
|
|
|
r, err := c.readOne(key, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return []*store.Record{}, err // preserve store.ErrNotFound
|
|
|
|
}
|
|
|
|
return []*store.Record{r}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) readOne(key string, opts ...store.ReadOption) (*store.Record, error) {
|
|
|
|
for i, s := range c.stores {
|
|
|
|
// ReadOne ignores all options
|
|
|
|
r, err := s.Read(key)
|
|
|
|
if err == nil {
|
|
|
|
if len(r) > 1 {
|
|
|
|
return nil, errors.Wrapf(err, "read from L%d cache (%s) returned multiple records", i, c.stores[i].String())
|
|
|
|
}
|
|
|
|
for j := i - 1; j >= 0; j-- {
|
|
|
|
err := c.stores[j].Write(r[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not write to L%d cache (%s)", j, c.stores[j].String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r[0], nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, store.ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) Write(r *store.Record, opts ...store.WriteOption) error {
|
|
|
|
// Write to all layers in reverse
|
|
|
|
for i := len(c.stores) - 1; i >= 0; i-- {
|
|
|
|
if err := c.stores[i].Write(r, opts...); err != nil {
|
|
|
|
return errors.Wrapf(err, "could not write to L%d cache (%s)", i, c.stores[i].String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) Delete(key string, opts ...store.DeleteOption) error {
|
|
|
|
for i, s := range c.stores {
|
|
|
|
if err := s.Delete(key, opts...); err != nil {
|
|
|
|
return errors.Wrapf(err, "could not delete from L%d cache (%s)", i, c.stores[i].String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) List(opts ...store.ListOption) ([]string, error) {
|
|
|
|
// List only makes sense from the top level
|
|
|
|
return c.stores[len(c.stores)-1].List(opts...)
|
|
|
|
}
|