github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/kv/store.go (about) 1 package kv 2 3 //go:generate go run github.com/golang/mock/mockgen@v1.6.0 -source=store.go -destination=mock/store.go -package=mock 4 5 import ( 6 "context" 7 "errors" 8 "fmt" 9 "strconv" 10 "strings" 11 "sync" 12 13 "github.com/treeverse/lakefs/pkg/kv/kvparams" 14 ) 15 16 // KV Schema versions 17 const ( 18 InitialMigrateVersion = iota + 1 19 ACLMigrateVersion 20 ACLNoReposMigrateVersion 21 ACLImportMigrateVersion 22 NextSchemaVersion 23 ) 24 25 const ( 26 PathDelimiter = "/" 27 MetadataPartitionKey = "kv-internal-metadata" 28 ) 29 30 var ( 31 ErrClosedEntries = errors.New("closed entries") 32 ErrConnectFailed = errors.New("connect failed") 33 ErrDriverConfiguration = errors.New("driver configuration") 34 ErrMissingPartitionKey = errors.New("missing partition key") 35 ErrMissingKey = errors.New("missing key") 36 ErrMissingValue = errors.New("missing value") 37 ErrNotFound = errors.New("not found") 38 ErrPredicateFailed = errors.New("predicate failed") 39 ErrSetupFailed = errors.New("setup failed") 40 ErrUnknownDriver = errors.New("unknown driver") 41 ErrTableNotActive = errors.New("table not active") 42 ErrSlowDown = errors.New("slow down") 43 ) 44 45 // Precond Type for special conditionals provided as predicates for the SetIf method 46 type Precond string 47 48 // PrecondConditionalExists Conditional for SetIf which performs Set only if key already exists in store 49 var PrecondConditionalExists = Precond("ConditionalExists") 50 51 func FormatPath(p ...string) string { 52 return strings.Join(p, PathDelimiter) 53 } 54 55 // Driver is the interface to access a kv database as a Store. Each kv provider implements a Driver. 56 type Driver interface { 57 // Open opens access to the database store. Implementations give access to the same storage based on the dsn. 58 // Implementation can return the same Storage instance based on dsn or new one as long as it provides access to 59 // the same storage. 60 Open(ctx context.Context, params kvparams.Config) (Store, error) 61 } 62 63 // Predicate value used to update a key base on a previous fetched value. 64 // 65 // Store's Get used to pull the key's value with the associated predicate. 66 // Store's SetIf used to set the key's value based on the predicate. 67 type Predicate interface{} 68 69 // ValueWithPredicate value with predicate - Value holds the data and Predicate a value used for conditional set. 70 // 71 // Get operation will return this struct, holding the key's information 72 // SetIf operation will use the Predicate for conditional set 73 type ValueWithPredicate struct { 74 Value []byte 75 Predicate Predicate 76 } 77 78 type ScanOptions struct { 79 // KeyStart start key to seek the scan 80 KeyStart []byte 81 // BatchSize used by each implementation to perform limited query or fetching of data while scanning. 82 // The 0 value means - use the default by the implementation. 83 BatchSize int 84 } 85 86 type Store interface { 87 // Get returns a result containing the Value and Predicate for the given key, or ErrNotFound if key doesn't exist 88 // Predicate can be used for SetIf operation 89 Get(ctx context.Context, partitionKey, key []byte) (*ValueWithPredicate, error) 90 91 // Set stores the given value, overwriting an existing value if one exists 92 Set(ctx context.Context, partitionKey, key, value []byte) error 93 94 // SetIf returns an ErrPredicateFailed error if the key with valuePredicate passed 95 // doesn't match the currently stored value. SetIf is a simple compare-and-swap operator: 96 // valuePredicate is either the existing value, or nil for no previous key exists. 97 // this is intentionally simplistic: we can model a better abstraction on top, keeping this interface simple for implementors 98 SetIf(ctx context.Context, partitionKey, key, value []byte, valuePredicate Predicate) error 99 100 // Delete will delete the key, no error in if key doesn't exist 101 Delete(ctx context.Context, partitionKey, key []byte) error 102 103 // Scan returns entries of partitionKey, by key order. 104 // 'options' holds optional parameters to control the batch size and the key to start the scan with. 105 Scan(ctx context.Context, partitionKey []byte, options ScanOptions) (EntriesIterator, error) 106 107 // Close access to the database store. After calling Close the instance is unusable. 108 Close() 109 } 110 111 // EntriesIterator used to enumerate over Scan results 112 type EntriesIterator interface { 113 // Next should be called first before access Entry. 114 // it will process the next entry and return true if there is one. 115 Next() bool 116 117 // SeekGE moves the iterator to the first Entry with a key equal or greater to the given key. 118 // A call to Next is required after calling this method. 119 SeekGE(key []byte) 120 121 // Entry current entry read after calling Next, set to nil in case of an error or no more entries. 122 Entry() *Entry 123 124 // Err set to last error by reading or parse the next entry. 125 Err() error 126 127 // Close should be called at the end of processing entries, required to release resources used to scan entries. 128 // After calling 'Close' the instance should not be used as the behaviour will not be defined. 129 Close() 130 } 131 132 // Entry holds a pair of key/value 133 type Entry struct { 134 PartitionKey []byte 135 Key []byte 136 Value []byte 137 } 138 139 func (e *Entry) String() string { 140 if e == nil { 141 return "Entry{nil}" 142 } 143 return fmt.Sprintf("Entry{%v, %v}", e.Key, e.Value) 144 } 145 146 // map drivers implementation 147 var ( 148 drivers = make(map[string]Driver) 149 driversMu sync.RWMutex 150 ) 151 152 // Register 'driver' implementation under 'name'. Panic in case of empty name, nil driver or name already registered. 153 func Register(name string, driver Driver) { 154 if name == "" { 155 panic("kv store register name is missing") 156 } 157 if driver == nil { 158 panic("kv store Register driver is nil") 159 } 160 driversMu.Lock() 161 defer driversMu.Unlock() 162 if _, found := drivers[name]; found { 163 panic("kv store Register driver already registered " + name) 164 } 165 drivers[name] = driver 166 } 167 168 // UnregisterAllDrivers remove all loaded drivers, used for test code. 169 func UnregisterAllDrivers() { 170 driversMu.Lock() 171 defer driversMu.Unlock() 172 for k := range drivers { 173 delete(drivers, k) 174 } 175 } 176 177 // Open lookup driver by 'type' and return store based on the configuration. 178 // Failed with ErrUnknownDriver in case 'name' is not registered 179 func Open(ctx context.Context, params kvparams.Config) (Store, error) { 180 driversMu.RLock() 181 d, ok := drivers[params.Type] 182 driversMu.RUnlock() 183 if !ok { 184 return nil, fmt.Errorf("%w: %s", ErrUnknownDriver, params.Type) 185 } 186 store, err := d.Open(ctx, params) 187 if err != nil { 188 return nil, err 189 } 190 return storeMetrics(store, params.Type), nil 191 } 192 193 // Drivers returns a list of registered drive names 194 func Drivers() []string { 195 driversMu.RLock() 196 defer driversMu.RUnlock() 197 names := make([]string, 0, len(drivers)) 198 for name := range drivers { 199 names = append(names, name) 200 } 201 return names 202 } 203 204 // GetDBSchemaVersion returns the current KV DB schema version 205 func GetDBSchemaVersion(ctx context.Context, store Store) (int, error) { 206 res, err := store.Get(ctx, []byte(MetadataPartitionKey), dbSchemaPath()) 207 if err != nil { 208 return -1, err 209 } 210 version, err := strconv.Atoi(string(res.Value)) 211 if err != nil { 212 return -1, err 213 } 214 return version, nil 215 } 216 217 // SetDBSchemaVersion sets KV DB schema version 218 func SetDBSchemaVersion(ctx context.Context, store Store, version uint) error { 219 return store.Set(ctx, []byte(MetadataPartitionKey), dbSchemaPath(), []byte(fmt.Sprintf("%d", version))) 220 } 221 222 func dbSchemaPath() []byte { 223 return []byte(FormatPath("kv", "schema", "version")) 224 } 225 226 func IsLatestSchemaVersion(version int) bool { 227 return version == NextSchemaVersion-1 228 }