github.com/creachadair/ffs@v0.17.3/blob/memstore/memstore.go (about)

     1  // Copyright 2019 Michael J. Fromberger. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package memstore implements the [blob.Store] and [blob.KV] interfaces using
    16  // in-memory dictionaries. This is primarily useful for testing, as the
    17  // contents are not persisted.
    18  package memstore
    19  
    20  import (
    21  	"cmp"
    22  	"context"
    23  	"iter"
    24  	"sync"
    25  
    26  	"github.com/creachadair/ffs/blob"
    27  	"github.com/creachadair/mds/stree"
    28  )
    29  
    30  // A Store implements the [blob.Store] interface using an in-memory dictionary
    31  // for each keyspace. A zero value is ready for use, but must not be copied
    32  // after its first use.
    33  type Store struct {
    34  	newKV func() blob.KV // Set on construction, read-only thereafter
    35  
    36  	μ    sync.Mutex
    37  	kvs  map[string]blob.KV
    38  	subs map[string]*Store
    39  }
    40  
    41  func (s *Store) kv() blob.KV {
    42  	if s.newKV == nil {
    43  		return NewKV()
    44  	}
    45  	return s.newKV()
    46  }
    47  
    48  // KV implements part of [blob.Store].
    49  // This implementation never reports an error.
    50  func (s *Store) KV(_ context.Context, name string) (blob.KV, error) {
    51  	s.μ.Lock()
    52  	defer s.μ.Unlock()
    53  	kv, ok := s.kvs[name]
    54  	if !ok {
    55  		kv = s.kv()
    56  		if s.kvs == nil {
    57  			s.kvs = make(map[string]blob.KV)
    58  		}
    59  		s.kvs[name] = kv
    60  	}
    61  	return kv, nil
    62  }
    63  
    64  // CAS implements part of [blob.Store].
    65  // This implementation never reports an error.
    66  func (s *Store) CAS(ctx context.Context, name string) (blob.CAS, error) {
    67  	return blob.CASFromKVError(s.KV(ctx, name))
    68  }
    69  
    70  // Sub implements part of [blob.Store].
    71  // This implementation never reports an error.
    72  func (s *Store) Sub(_ context.Context, name string) (blob.Store, error) {
    73  	s.μ.Lock()
    74  	defer s.μ.Unlock()
    75  	sub, ok := s.subs[name]
    76  	if !ok {
    77  		sub = &Store{newKV: s.newKV}
    78  		if s.subs == nil {
    79  			s.subs = make(map[string]*Store)
    80  		}
    81  		s.subs[name] = sub
    82  	}
    83  	return sub, nil
    84  }
    85  
    86  // Close implements part of [blob.StoreCloser]. This implementation is a no-op.
    87  func (*Store) Close(context.Context) error { return nil }
    88  
    89  // New constructs a new empty Store that uses newKV to construct keyspaces.
    90  // If newKV == nil, [NewKV] is used.
    91  func New(newKV func() blob.KV) *Store {
    92  	return &Store{kvs: make(map[string]blob.KV), newKV: newKV}
    93  }
    94  
    95  // KV implements the [blob.KV] interface using an in-memory dictionary. The
    96  // contents of a Store are not persisted. All operations on a memstore are safe
    97  // for concurrent use by multiple goroutines.
    98  type KV struct {
    99  	μ sync.RWMutex
   100  	m *stree.Tree[entry]
   101  }
   102  
   103  // An entry is a pair of a string key and value.  The value is not part of the
   104  // comparison key.
   105  type entry = stree.KV[string, string]
   106  
   107  // Opener constructs a [blob.StoreCloser] for use with the [store] package.
   108  // The concrete type of the result is [memstore.Store]. The address is ignored,
   109  // and an error is never returned.
   110  //
   111  // [store]: https://godoc.org/github.com/creachadair/ffstools/lib/store
   112  func Opener(_ context.Context, _ string) (blob.StoreCloser, error) { return New(nil), nil }
   113  
   114  // NewKV constructs a new, empty key-value namespace.
   115  func NewKV() *KV { return &KV{m: stree.New(300, entry{}.Compare(cmp.Compare))} }
   116  
   117  // Clear removes all keys and values from s.
   118  func (s *KV) Clear() {
   119  	s.μ.Lock()
   120  	defer s.μ.Unlock()
   121  	s.m.Clear()
   122  }
   123  
   124  // Snapshot copies a snapshot of the keys and values of s into m.
   125  // If m == nil, a new empty map is allocated and returned.
   126  // It returns m to allow chaining with construction.
   127  func (s *KV) Snapshot(m map[string]string) map[string]string {
   128  	if m == nil {
   129  		m = make(map[string]string)
   130  	}
   131  	s.μ.RLock()
   132  	defer s.μ.RUnlock()
   133  	for e := range s.m.Inorder {
   134  		m[e.Key] = e.Value
   135  	}
   136  	return m
   137  }
   138  
   139  // Init replaces the contents of s with the keys and values in m.
   140  // It returns s to permit chaining with construction.
   141  func (s *KV) Init(m map[string]string) *KV {
   142  	s.μ.Lock()
   143  	defer s.μ.Unlock()
   144  	s.m.Clear()
   145  	for key, val := range m {
   146  		s.m.Add(entry{Key: key, Value: val})
   147  	}
   148  	return s
   149  }
   150  
   151  // Get implements part of [blob.KV].
   152  func (s *KV) Get(_ context.Context, key string) ([]byte, error) {
   153  	s.μ.RLock()
   154  	defer s.μ.RUnlock()
   155  
   156  	if e, ok := s.m.Get(entry{Key: key}); ok {
   157  		return []byte(e.Value), nil
   158  	}
   159  	return nil, blob.KeyNotFound(key)
   160  }
   161  
   162  // Has implements part of [blob.KV].
   163  func (s *KV) Has(_ context.Context, keys ...string) (blob.KeySet, error) {
   164  	s.μ.RLock()
   165  	defer s.μ.RUnlock()
   166  	out := make(blob.KeySet)
   167  	for _, key := range keys {
   168  		if _, ok := s.m.Get(entry{Key: key}); ok {
   169  			out.Add(key)
   170  		}
   171  	}
   172  	return out, nil
   173  }
   174  
   175  // Put implements part of [blob.KV].
   176  func (s *KV) Put(_ context.Context, opts blob.PutOptions) error {
   177  	s.μ.Lock()
   178  	defer s.μ.Unlock()
   179  
   180  	ent := entry{Key: opts.Key, Value: string(opts.Data)}
   181  	if opts.Replace {
   182  		s.m.Replace(ent)
   183  	} else if !s.m.Add(ent) {
   184  		return blob.KeyExists(opts.Key)
   185  	}
   186  	return nil
   187  }
   188  
   189  // Delete implements part of [blob.KV].
   190  func (s *KV) Delete(_ context.Context, key string) error {
   191  	s.μ.Lock()
   192  	defer s.μ.Unlock()
   193  
   194  	if !s.m.Remove(entry{Key: key}) {
   195  		return blob.KeyNotFound(key)
   196  	}
   197  	return nil
   198  }
   199  
   200  // List implements part of [blob.KV].
   201  func (s *KV) List(_ context.Context, start string) iter.Seq2[string, error] {
   202  	return func(yield func(string, error) bool) {
   203  		cur, ok := s.firstKey(start)
   204  		for ok {
   205  			if !yield(cur.Key, nil) {
   206  				return
   207  			}
   208  			cur, ok = s.nextKey(cur)
   209  		}
   210  	}
   211  }
   212  
   213  func (s *KV) firstKey(start string) (entry, bool) {
   214  	s.μ.RLock()
   215  	defer s.μ.RUnlock()
   216  	return s.m.GetNearest(entry{Key: start})
   217  }
   218  
   219  func (s *KV) nextKey(prev entry) (entry, bool) {
   220  	s.μ.RLock()
   221  	defer s.μ.RUnlock()
   222  	return s.m.GetNext(prev)
   223  }
   224  
   225  // Len implements part of [blob.KV].
   226  func (s *KV) Len(context.Context) (int64, error) {
   227  	s.μ.RLock()
   228  	defer s.μ.RUnlock()
   229  	return int64(s.m.Len()), nil
   230  }