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 }