153 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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/micro/go-micro/v2/store/memory"
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| type cache struct {
 | |
| 	stores []store.Store
 | |
| }
 | |
| 
 | |
| // Cache is a cpu register style cache for the store.
 | |
| // It syncs between N stores in a faulting manner.
 | |
| type Cache interface {
 | |
| 	// Implements the store interface
 | |
| 	store.Store
 | |
| }
 | |
| 
 | |
| // NewCache returns a new store using the underlying stores, which must be already Init()ialised
 | |
| func NewCache(stores ...store.Store) Cache {
 | |
| 	if len(stores) == 0 {
 | |
| 		stores = []store.Store{
 | |
| 			memory.NewStore(),
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// TODO: build in an in memory cache
 | |
| 	c := &cache{
 | |
| 		stores: stores,
 | |
| 	}
 | |
| 
 | |
| 	return c
 | |
| }
 | |
| 
 | |
| func (c *cache) Close() error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *cache) Init(opts ...store.Option) error {
 | |
| 	// pass to the stores
 | |
| 	for _, store := range c.stores {
 | |
| 		if err := store.Init(opts...); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *cache) Options() store.Options {
 | |
| 	// return from first store
 | |
| 	return c.stores[0].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...)
 | |
| }
 |