| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package cachememdb |
| |
| import ( |
| "errors" |
| "fmt" |
| "sync/atomic" |
| |
| memdb "github.com/hashicorp/go-memdb" |
| ) |
| |
| const ( |
| tableNameIndexer = "indexer" |
| ) |
| |
| // CacheMemDB is the underlying cache database for storing indexes. |
| type CacheMemDB struct { |
| db *atomic.Value |
| } |
| |
| // New creates a new instance of CacheMemDB. |
| func New() (*CacheMemDB, error) { |
| db, err := newDB() |
| if err != nil { |
| return nil, err |
| } |
| |
| c := &CacheMemDB{ |
| db: new(atomic.Value), |
| } |
| c.db.Store(db) |
| |
| return c, nil |
| } |
| |
| func newDB() (*memdb.MemDB, error) { |
| cacheSchema := &memdb.DBSchema{ |
| Tables: map[string]*memdb.TableSchema{ |
| tableNameIndexer: { |
| Name: tableNameIndexer, |
| Indexes: map[string]*memdb.IndexSchema{ |
| // This index enables fetching the cached item based on the |
| // identifier of the index. |
| IndexNameID: { |
| Name: IndexNameID, |
| Unique: true, |
| Indexer: &memdb.StringFieldIndex{ |
| Field: "ID", |
| }, |
| }, |
| // This index enables fetching all the entries in cache for |
| // a given request path, in a given namespace. |
| IndexNameRequestPath: { |
| Name: IndexNameRequestPath, |
| Unique: false, |
| Indexer: &memdb.CompoundIndex{ |
| Indexes: []memdb.Indexer{ |
| &memdb.StringFieldIndex{ |
| Field: "Namespace", |
| }, |
| &memdb.StringFieldIndex{ |
| Field: "RequestPath", |
| }, |
| }, |
| }, |
| }, |
| // This index enables fetching all the entries in cache |
| // belonging to the leases of a given token. |
| IndexNameLeaseToken: { |
| Name: IndexNameLeaseToken, |
| Unique: false, |
| AllowMissing: true, |
| Indexer: &memdb.StringFieldIndex{ |
| Field: "LeaseToken", |
| }, |
| }, |
| // This index enables fetching all the entries in cache |
| // that are tied to the given token, regardless of the |
| // entries belonging to the token or belonging to the |
| // lease. |
| IndexNameToken: { |
| Name: IndexNameToken, |
| Unique: true, |
| AllowMissing: true, |
| Indexer: &memdb.StringFieldIndex{ |
| Field: "Token", |
| }, |
| }, |
| // This index enables fetching all the entries in cache for |
| // the given parent token. |
| IndexNameTokenParent: { |
| Name: IndexNameTokenParent, |
| Unique: false, |
| AllowMissing: true, |
| Indexer: &memdb.StringFieldIndex{ |
| Field: "TokenParent", |
| }, |
| }, |
| // This index enables fetching all the entries in cache for |
| // the given accessor. |
| IndexNameTokenAccessor: { |
| Name: IndexNameTokenAccessor, |
| Unique: true, |
| AllowMissing: true, |
| Indexer: &memdb.StringFieldIndex{ |
| Field: "TokenAccessor", |
| }, |
| }, |
| // This index enables fetching all the entries in cache for |
| // the given lease identifier. |
| IndexNameLease: { |
| Name: IndexNameLease, |
| Unique: true, |
| AllowMissing: true, |
| Indexer: &memdb.StringFieldIndex{ |
| Field: "Lease", |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| db, err := memdb.NewMemDB(cacheSchema) |
| if err != nil { |
| return nil, err |
| } |
| return db, nil |
| } |
| |
| // Get returns the index based on the indexer and the index values provided. |
| func (c *CacheMemDB) Get(indexName string, indexValues ...interface{}) (*Index, error) { |
| if !validIndexName(indexName) { |
| return nil, fmt.Errorf("invalid index name %q", indexName) |
| } |
| |
| txn := c.db.Load().(*memdb.MemDB).Txn(false) |
| |
| raw, err := txn.First(tableNameIndexer, indexName, indexValues...) |
| if err != nil { |
| return nil, err |
| } |
| |
| if raw == nil { |
| return nil, nil |
| } |
| |
| index, ok := raw.(*Index) |
| if !ok { |
| return nil, errors.New("unable to parse index value from the cache") |
| } |
| |
| return index, nil |
| } |
| |
| // Set stores the index into the cache. |
| func (c *CacheMemDB) Set(index *Index) error { |
| if index == nil { |
| return errors.New("nil index provided") |
| } |
| |
| txn := c.db.Load().(*memdb.MemDB).Txn(true) |
| defer txn.Abort() |
| |
| if err := txn.Insert(tableNameIndexer, index); err != nil { |
| return fmt.Errorf("unable to insert index into cache: %v", err) |
| } |
| |
| txn.Commit() |
| |
| return nil |
| } |
| |
| // GetByPrefix returns all the cached indexes based on the index name and the |
| // value prefix. |
| func (c *CacheMemDB) GetByPrefix(indexName string, indexValues ...interface{}) ([]*Index, error) { |
| if !validIndexName(indexName) { |
| return nil, fmt.Errorf("invalid index name %q", indexName) |
| } |
| |
| indexName = indexName + "_prefix" |
| |
| // Get all the objects |
| txn := c.db.Load().(*memdb.MemDB).Txn(false) |
| |
| iter, err := txn.Get(tableNameIndexer, indexName, indexValues...) |
| if err != nil { |
| return nil, err |
| } |
| |
| var indexes []*Index |
| for { |
| obj := iter.Next() |
| if obj == nil { |
| break |
| } |
| index, ok := obj.(*Index) |
| if !ok { |
| return nil, fmt.Errorf("failed to cast cached index") |
| } |
| |
| indexes = append(indexes, index) |
| } |
| |
| return indexes, nil |
| } |
| |
| // Evict removes an index from the cache based on index name and value. |
| func (c *CacheMemDB) Evict(indexName string, indexValues ...interface{}) error { |
| index, err := c.Get(indexName, indexValues...) |
| if err != nil { |
| return fmt.Errorf("unable to fetch index on cache deletion: %v", err) |
| } |
| |
| if index == nil { |
| return nil |
| } |
| |
| txn := c.db.Load().(*memdb.MemDB).Txn(true) |
| defer txn.Abort() |
| |
| if err := txn.Delete(tableNameIndexer, index); err != nil { |
| return fmt.Errorf("unable to delete index from cache: %v", err) |
| } |
| |
| txn.Commit() |
| |
| return nil |
| } |
| |
| // Flush resets the underlying cache object. |
| func (c *CacheMemDB) Flush() error { |
| newDB, err := newDB() |
| if err != nil { |
| return err |
| } |
| |
| c.db.Store(newDB) |
| |
| return nil |
| } |