github.com/MetalBlockchain/metalgo@v1.11.9/database/rpcdb/db_client.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package rpcdb 5 6 import ( 7 "context" 8 "encoding/json" 9 "sync" 10 11 "google.golang.org/protobuf/types/known/emptypb" 12 13 "github.com/MetalBlockchain/metalgo/database" 14 "github.com/MetalBlockchain/metalgo/utils" 15 "github.com/MetalBlockchain/metalgo/utils/set" 16 17 rpcdbpb "github.com/MetalBlockchain/metalgo/proto/pb/rpcdb" 18 ) 19 20 var ( 21 _ database.Database = (*DatabaseClient)(nil) 22 _ database.Batch = (*batch)(nil) 23 _ database.Iterator = (*iterator)(nil) 24 ) 25 26 // DatabaseClient is an implementation of database that talks over RPC. 27 type DatabaseClient struct { 28 client rpcdbpb.DatabaseClient 29 30 closed utils.Atomic[bool] 31 } 32 33 // NewClient returns a database instance connected to a remote database instance 34 func NewClient(client rpcdbpb.DatabaseClient) *DatabaseClient { 35 return &DatabaseClient{client: client} 36 } 37 38 // Has attempts to return if the database has a key with the provided value. 39 func (db *DatabaseClient) Has(key []byte) (bool, error) { 40 resp, err := db.client.Has(context.Background(), &rpcdbpb.HasRequest{ 41 Key: key, 42 }) 43 if err != nil { 44 return false, err 45 } 46 return resp.Has, ErrEnumToError[resp.Err] 47 } 48 49 // Get attempts to return the value that was mapped to the key that was provided 50 func (db *DatabaseClient) Get(key []byte) ([]byte, error) { 51 resp, err := db.client.Get(context.Background(), &rpcdbpb.GetRequest{ 52 Key: key, 53 }) 54 if err != nil { 55 return nil, err 56 } 57 return resp.Value, ErrEnumToError[resp.Err] 58 } 59 60 // Put attempts to set the value this key maps to 61 func (db *DatabaseClient) Put(key, value []byte) error { 62 resp, err := db.client.Put(context.Background(), &rpcdbpb.PutRequest{ 63 Key: key, 64 Value: value, 65 }) 66 if err != nil { 67 return err 68 } 69 return ErrEnumToError[resp.Err] 70 } 71 72 // Delete attempts to remove any mapping from the key 73 func (db *DatabaseClient) Delete(key []byte) error { 74 resp, err := db.client.Delete(context.Background(), &rpcdbpb.DeleteRequest{ 75 Key: key, 76 }) 77 if err != nil { 78 return err 79 } 80 return ErrEnumToError[resp.Err] 81 } 82 83 // NewBatch returns a new batch 84 func (db *DatabaseClient) NewBatch() database.Batch { 85 return &batch{db: db} 86 } 87 88 func (db *DatabaseClient) NewIterator() database.Iterator { 89 return db.NewIteratorWithStartAndPrefix(nil, nil) 90 } 91 92 func (db *DatabaseClient) NewIteratorWithStart(start []byte) database.Iterator { 93 return db.NewIteratorWithStartAndPrefix(start, nil) 94 } 95 96 func (db *DatabaseClient) NewIteratorWithPrefix(prefix []byte) database.Iterator { 97 return db.NewIteratorWithStartAndPrefix(nil, prefix) 98 } 99 100 // NewIteratorWithStartAndPrefix returns a new empty iterator 101 func (db *DatabaseClient) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator { 102 resp, err := db.client.NewIteratorWithStartAndPrefix(context.Background(), &rpcdbpb.NewIteratorWithStartAndPrefixRequest{ 103 Start: start, 104 Prefix: prefix, 105 }) 106 if err != nil { 107 return &database.IteratorError{ 108 Err: err, 109 } 110 } 111 return newIterator(db, resp.Id) 112 } 113 114 // Compact attempts to optimize the space utilization in the provided range 115 func (db *DatabaseClient) Compact(start, limit []byte) error { 116 resp, err := db.client.Compact(context.Background(), &rpcdbpb.CompactRequest{ 117 Start: start, 118 Limit: limit, 119 }) 120 if err != nil { 121 return err 122 } 123 return ErrEnumToError[resp.Err] 124 } 125 126 // Close attempts to close the database 127 func (db *DatabaseClient) Close() error { 128 db.closed.Set(true) 129 resp, err := db.client.Close(context.Background(), &rpcdbpb.CloseRequest{}) 130 if err != nil { 131 return err 132 } 133 return ErrEnumToError[resp.Err] 134 } 135 136 func (db *DatabaseClient) HealthCheck(ctx context.Context) (interface{}, error) { 137 health, err := db.client.HealthCheck(ctx, &emptypb.Empty{}) 138 if err != nil { 139 return nil, err 140 } 141 142 return json.RawMessage(health.Details), nil 143 } 144 145 type batch struct { 146 database.BatchOps 147 148 db *DatabaseClient 149 } 150 151 func (b *batch) Write() error { 152 request := &rpcdbpb.WriteBatchRequest{} 153 keySet := set.NewSet[string](len(b.Ops)) 154 for i := len(b.Ops) - 1; i >= 0; i-- { 155 op := b.Ops[i] 156 key := string(op.Key) 157 if keySet.Contains(key) { 158 continue 159 } 160 keySet.Add(key) 161 162 if op.Delete { 163 request.Deletes = append(request.Deletes, &rpcdbpb.DeleteRequest{ 164 Key: op.Key, 165 }) 166 } else { 167 request.Puts = append(request.Puts, &rpcdbpb.PutRequest{ 168 Key: op.Key, 169 Value: op.Value, 170 }) 171 } 172 } 173 174 resp, err := b.db.client.WriteBatch(context.Background(), request) 175 if err != nil { 176 return err 177 } 178 return ErrEnumToError[resp.Err] 179 } 180 181 func (b *batch) Inner() database.Batch { 182 return b 183 } 184 185 type iterator struct { 186 db *DatabaseClient 187 id uint64 188 189 data []*rpcdbpb.PutRequest 190 fetchedData chan []*rpcdbpb.PutRequest 191 192 errLock sync.RWMutex 193 err error 194 195 reqUpdateError chan chan struct{} 196 197 once sync.Once 198 onClose chan struct{} 199 onClosed chan struct{} 200 } 201 202 func newIterator(db *DatabaseClient, id uint64) *iterator { 203 it := &iterator{ 204 db: db, 205 id: id, 206 fetchedData: make(chan []*rpcdbpb.PutRequest), 207 reqUpdateError: make(chan chan struct{}), 208 onClose: make(chan struct{}), 209 onClosed: make(chan struct{}), 210 } 211 go it.fetch() 212 return it 213 } 214 215 // Invariant: fetch is the only thread with access to send requests to the 216 // server's iterator. This is needed because iterators are not thread safe and 217 // the server expects the client (us) to only ever issue one request at a time 218 // for a given iterator id. 219 func (it *iterator) fetch() { 220 defer func() { 221 resp, err := it.db.client.IteratorRelease(context.Background(), &rpcdbpb.IteratorReleaseRequest{ 222 Id: it.id, 223 }) 224 if err != nil { 225 it.setError(err) 226 } else { 227 it.setError(ErrEnumToError[resp.Err]) 228 } 229 230 close(it.fetchedData) 231 close(it.onClosed) 232 }() 233 234 for { 235 resp, err := it.db.client.IteratorNext(context.Background(), &rpcdbpb.IteratorNextRequest{ 236 Id: it.id, 237 }) 238 if err != nil { 239 it.setError(err) 240 return 241 } 242 243 if len(resp.Data) == 0 { 244 return 245 } 246 247 for { 248 select { 249 case it.fetchedData <- resp.Data: 250 case onUpdated := <-it.reqUpdateError: 251 it.updateError() 252 close(onUpdated) 253 continue 254 case <-it.onClose: 255 return 256 } 257 break 258 } 259 } 260 } 261 262 // Next attempts to move the iterator to the next element and returns if this 263 // succeeded 264 func (it *iterator) Next() bool { 265 if it.db.closed.Get() { 266 it.data = nil 267 it.setError(database.ErrClosed) 268 return false 269 } 270 if len(it.data) > 1 { 271 it.data[0] = nil 272 it.data = it.data[1:] 273 return true 274 } 275 276 it.data = <-it.fetchedData 277 return len(it.data) > 0 278 } 279 280 // Error returns any that occurred while iterating 281 func (it *iterator) Error() error { 282 if err := it.getError(); err != nil { 283 return err 284 } 285 286 onUpdated := make(chan struct{}) 287 select { 288 case it.reqUpdateError <- onUpdated: 289 <-onUpdated 290 case <-it.onClosed: 291 } 292 293 return it.getError() 294 } 295 296 // Key returns the key of the current element 297 func (it *iterator) Key() []byte { 298 if len(it.data) == 0 { 299 return nil 300 } 301 return it.data[0].Key 302 } 303 304 // Value returns the value of the current element 305 func (it *iterator) Value() []byte { 306 if len(it.data) == 0 { 307 return nil 308 } 309 return it.data[0].Value 310 } 311 312 // Release frees any resources held by the iterator 313 func (it *iterator) Release() { 314 it.once.Do(func() { 315 close(it.onClose) 316 <-it.onClosed 317 }) 318 } 319 320 func (it *iterator) updateError() { 321 resp, err := it.db.client.IteratorError(context.Background(), &rpcdbpb.IteratorErrorRequest{ 322 Id: it.id, 323 }) 324 if err != nil { 325 it.setError(err) 326 } else { 327 it.setError(ErrEnumToError[resp.Err]) 328 } 329 } 330 331 func (it *iterator) setError(err error) { 332 if err == nil { 333 return 334 } 335 336 it.errLock.Lock() 337 defer it.errLock.Unlock() 338 339 if it.err == nil { 340 it.err = err 341 } 342 } 343 344 func (it *iterator) getError() error { 345 it.errLock.RLock() 346 defer it.errLock.RUnlock() 347 348 return it.err 349 }