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  }