kv

package module
v1.18.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 13, 2026 License: BSD-2-Clause Imports: 16 Imported by: 14

README

KV - Key-Value Store Abstraction Library

A Go library providing unified interfaces for key-value stores. This library abstracts different KV database implementations (BadgerDB, BoltDB, in-memory) behind common interfaces, enabling easy switching between backends without code changes.

Features

  • Unified Interface: Common API for different key-value stores
  • Generic Type Safety: Full Go 1.18+ generics support for type-safe operations
  • Transaction Support: Read and write transactions with proper error handling
  • Bucket Management: Organized data storage with bucket abstraction
  • Metrics Integration: Built-in Prometheus metrics support
  • Relation Store: Bidirectional 1:N relationship management
  • Benchmarking: Performance testing framework included

Quick Start

Installation
go get github.com/bborbe/kv
Basic Usage
package main

import (
    "context"
    "fmt"
    "github.com/bborbe/kv"
    "github.com/bborbe/badgerkv" // or boltkv, memorykv
)

type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
    Email string `json:"email"`
}

func main() {
    ctx := context.Background()
    
    // Create database instance (example with badgerkv)
    db, err := badgerkv.Open("/tmp/mydb")
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    // Create a type-safe store
    userStore := kv.NewStore[string, User](db, kv.BucketName("users"))
    
    // Add a user
    user := User{ID: "123", Name: "Alice", Email: "[email protected]"}
    if err := userStore.Add(ctx, user.ID, user); err != nil {
        panic(err)
    }
    
    // Get a user
    retrievedUser, err := userStore.Get(ctx, "123")
    if err != nil {
        panic(err)
    }
    fmt.Printf("User: %+v\n", *retrievedUser)
    
    // Check if user exists
    exists, err := userStore.Exists(ctx, "123")
    if err != nil {
        panic(err)
    }
    fmt.Printf("User exists: %v\n", exists)
}
Using Transactions
// Manual transaction control
err := db.Update(ctx, func(ctx context.Context, tx kv.Tx) error {
    bucket, err := tx.CreateBucket(kv.BucketName("users"))
    if err != nil {
        return err
    }
    
    userData, _ := json.Marshal(user)
    return bucket.Put(ctx, []byte("123"), userData)
})
Iterating Over Data
// Stream all users
userCh := make(chan User, 10)
go func() {
    defer close(userCh)
    userStore.Stream(ctx, userCh)
}()

for user := range userCh {
    fmt.Printf("User: %+v\n", user)
}

// Or map over all users
userStore.Map(ctx, func(ctx context.Context, key string, user User) error {
    fmt.Printf("Key: %s, User: %+v\n", key, user)
    return nil
})

Architecture

Interface Hierarchy

The library uses a layered interface approach:

  1. DB Interface - Database lifecycle management

    • Update() - Write transactions
    • View() - Read-only transactions
    • Sync(), Close(), Remove() - Database management
  2. Transaction Interface - Bucket operations within transactions

    • Bucket(), CreateBucket(), DeleteBucket() - Bucket management
    • ListBucketNames() - Bucket enumeration
  3. Bucket Interface - Key-value operations

    • Put(), Get(), Delete() - Basic operations
    • Iterator(), IteratorReverse() - Iteration support
  4. Generic Store Layer - Type-safe operations

    • Uses Go generics: Store[KEY ~[]byte | ~string, OBJECT any]
    • Automatic JSON marshaling/unmarshaling
    • Composed interfaces: StoreAdder, StoreGetter, StoreRemover, etc.
Advanced Features
Metrics Wrapper

Monitor your database operations with Prometheus metrics:

import "github.com/prometheus/client_golang/prometheus"

// Wrap your DB with metrics
metricsDB := kv.NewDBWithMetrics(db, prometheus.DefaultRegisterer, "myapp")
Relation Store

Manage bidirectional 1:N relationships:

relationStore := kv.NewRelationStore[string, string](db, kv.BucketName("user_groups"))

// Add relationships
relationStore.Add(ctx, "user123", "group456")
relationStore.Add(ctx, "user123", "group789")

// Query relationships
groups, err := relationStore.GetByA(ctx, "user123") // Returns: ["group456", "group789"]
users, err := relationStore.GetByB(ctx, "group456") // Returns: ["user123"]

Implementations

This library defines interfaces implemented by three concrete packages:

  • badgerkv - BadgerDB implementation (LSM-tree, high performance)
  • boltkv - BoltDB implementation (B+ tree, ACID compliance)
  • memorykv - In-memory implementation (testing/development)
Switching Implementations
// BadgerDB for high-performance scenarios
db, err := badgerkv.Open("/path/to/badger")

// BoltDB for ACID compliance
db, err := boltkv.Open("/path/to/bolt.db")

// In-memory for testing
db := memorykv.New()

Testing

The library includes comprehensive test suites that can be reused for implementations:

import "github.com/bborbe/kv"

// Use the basic test suite for your implementation
var _ = Describe("MyKV", func() {
    kv.BasicTestSuite(func() kv.Provider {
        return mykvProvider{}
    })
})

Development

Prerequisites
  • Go 1.18+ (for generics support)
Commands
# Full pre-commit pipeline
make precommit

# Run tests
make test

# Format code
make format

# Generate mocks
make generate

# Check code quality
make check

License

This project is licensed under the BSD-style license. See the LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var BucketAlreadyExistsError = ErrBucketAlreadyExists

BucketAlreadyExistsError is deprecated: use ErrBucketAlreadyExists instead.

View Source
var BucketNotFoundError = ErrBucketNotFound

BucketNotFoundError is deprecated: use ErrBucketNotFound instead.

View Source
var ErrBucketAlreadyExists = errors.New("bucket already exists")

ErrBucketAlreadyExists is returned when attempting to create a bucket that already exists.

View Source
var ErrBucketNotFound = errors.New("bucket not found")

ErrBucketNotFound is returned when attempting to access a bucket that does not exist.

View Source
var ErrKeyNotFound = errors.New("key not found")

ErrKeyNotFound is currently not returned, but can be used as common error.

View Source
var ErrTransactionAlreadyOpen = errors.New("transaction already open")

ErrTransactionAlreadyOpen is returned when attempting to open a transaction while one is already active.

View Source
var KeyNotFoundError = ErrKeyNotFound

KeyNotFoundError is deprecated: use ErrKeyNotFound instead.

View Source
var TransactionAlreadyOpenError = ErrTransactionAlreadyOpen

TransactionAlreadyOpenError is deprecated: use ErrTransactionAlreadyOpen instead.

Functions

func BasicTestSuite

func BasicTestSuite(provider Provider)

BasicTestSuite provides a comprehensive test suite for basic database operations that can be reused across different KV implementations.

func BucketTestSuite

func BucketTestSuite(provider Provider)

func Count

func Count(ctx context.Context, bucket Bucket) (int64, error)

Count returns the total number of items in the bucket. Returns -1 if context is cancelled.

func ForEach

func ForEach(
	ctx context.Context,
	bucket Bucket,
	fn func(item Item) error,
) error

ForEach iterates through all items in the bucket and executes the provided function for each item. Iteration stops early if the context is cancelled or if the function returns an error.

func IteratorTestSuite

func IteratorTestSuite(provider Provider)

func NewResetBucketHandler

func NewResetBucketHandler(db DB, cancel context.CancelFunc) http.Handler

NewResetBucketHandler returns a http.Handler that allow delete a bucket

func NewResetHandler

func NewResetHandler(db DB, cancel context.CancelFunc) http.Handler

NewResetHandler returns a http.Handler that allow delete the complete database

func RelationStoreTestSuite

func RelationStoreTestSuite(provider Provider)

Types

type Bucket

type Bucket interface {
	Put(ctx context.Context, key []byte, value []byte) error
	Get(ctx context.Context, bytes []byte) (Item, error)
	Delete(ctx context.Context, bytes []byte) error
	Iterator() Iterator
	IteratorReverse() Iterator
}

Bucket represents a key-value bucket within a transaction that supports CRUD operations and iteration.

type BucketName

type BucketName []byte

BucketName represents a bucket identifier as a byte slice.

func BucketFromStrings

func BucketFromStrings(values ...string) BucketName

BucketFromStrings creates a bucket name by joining multiple strings with underscores.

func NewBucketName

func NewBucketName(name string) BucketName

NewBucketName creates a new bucket name from a string.

func (BucketName) Bytes

func (b BucketName) Bytes() []byte

Bytes returns the bucket name as a byte slice.

func (BucketName) Equal

func (b BucketName) Equal(value BucketName) bool

Equal compares two bucket names for equality.

func (BucketName) String

func (b BucketName) String() string

String returns the bucket name as a string.

type BucketNames

type BucketNames []BucketName

BucketNames represents a collection of bucket names with utility methods.

func (BucketNames) Contains

func (t BucketNames) Contains(value BucketName) bool

Contains checks if the collection contains the specified bucket name.

type DB

type DB interface {
	// Update opens a write transaction
	Update(ctx context.Context, fn func(ctx context.Context, tx Tx) error) error

	// View opens a read only transaction
	View(ctx context.Context, fn func(ctx context.Context, tx Tx) error) error

	// Sync database to disk
	Sync() error

	// Close database
	Close() error

	// Remove database files from disk
	Remove() error
}

DB represents a key-value database that supports transactions and lifecycle management.

func NewDBWithMetrics added in v1.13.0

func NewDBWithMetrics(
	db DB,
	metrics Metrics,
) DB

NewDBWithMetrics wraps a DB instance with metrics collection for monitoring database operations.

type FuncTx added in v1.14.0

type FuncTx func(ctx context.Context, tx Tx) error

FuncTx is a function type that implements RunnableTx for executing transaction logic.

func (FuncTx) Run added in v1.14.0

func (r FuncTx) Run(ctx context.Context, tx Tx) error

Run implements the RunnableTx interface by executing the function.

type Item

type Item interface {
	Exists() bool
	Key() []byte
	Value(fn func(val []byte) error) error
}

Item represents a key-value pair retrieved from a bucket with existence checking.

func NewByteItem

func NewByteItem(
	key []byte,
	value []byte,
) Item

NewByteItem creates a new Item from raw byte slices for key and value.

type Iterator

type Iterator interface {
	Close()
	Item() Item
	Next()
	Valid() bool
	Rewind()
	Seek(key []byte)
}

Iterator provides sequential access to items in a bucket with navigation controls.

type Key

type Key []byte

Key represents a key in the key-value store as a byte slice.

func (Key) Bytes

func (f Key) Bytes() []byte

Bytes returns the key as a byte slice.

func (Key) String

func (f Key) String() string

String returns the key as a string.

type Metrics added in v1.13.0

type Metrics interface {
	DbUpdateInc()
	DbViewInc()
}

Metrics provides monitoring capabilities for database operations using Prometheus.

func NewMetrics added in v1.13.0

func NewMetrics() Metrics

NewMetrics creates a new Metrics instance with default Prometheus counters.

type Provider

type Provider interface {
	Get(ctx context.Context) (DB, error)
}

Provider defines a factory interface for creating database instances.

type ProviderFunc

type ProviderFunc func(ctx context.Context) (DB, error)

ProviderFunc is a function type that implements the Provider interface.

func (ProviderFunc) Get

func (p ProviderFunc) Get(ctx context.Context) (DB, error)

Get implements the Provider interface for ProviderFunc.

type RelationStore

type RelationStore[ID ~[]byte | ~string, RelatedID ~[]byte | ~string] interface {
	// Add the given relationIDs to ID
	Add(ctx context.Context, id ID, relatedIDs []RelatedID) error
	// Replace all relations of id with the given
	Replace(ctx context.Context, id ID, relatedIDs []RelatedID) error
	// Remove all relation from ID to the given
	Remove(ctx context.Context, id ID, relatedIDs []RelatedID) error
	// Delete ID and all relations
	Delete(ctx context.Context, id ID) error
	// RelatedIDs return all relation of ID
	RelatedIDs(ctx context.Context, id ID) ([]RelatedID, error)
	// IDs return all ids of RelatedID
	IDs(ctx context.Context, relatedID RelatedID) ([]ID, error)
	// StreamIDs return all existing IDs
	StreamIDs(ctx context.Context, ch chan<- ID) error
	// StreamRelatedIDs return all existing relationIDs
	StreamRelatedIDs(ctx context.Context, ch chan<- RelatedID) error
	// MapIDRelations maps all entry to the given func
	MapIDRelations(
		ctx context.Context,
		fn func(ctx context.Context, key ID, relatedIDs []RelatedID) error,
	) error
	// MapRelationIDs maps all entry to the given func
	MapRelationIDs(
		ctx context.Context,
		fn func(ctx context.Context, key RelatedID, ids []ID) error,
	) error
	// Invert returns the same store with flipped ID <-> RelationID
	Invert() RelationStore[RelatedID, ID]
}

RelationStore implement a forward and backword id lookup for a 1:N relation.

func NewRelationStore

func NewRelationStore[ID ~[]byte | ~string, RelatedID ~[]byte | ~string](
	db DB,
	name string,
) RelationStore[ID, RelatedID]

func NewRelationStoreFromRelationStoreTx added in v1.12.0

func NewRelationStoreFromRelationStoreTx[ID ~[]byte | ~string, RelatedID ~[]byte | ~string](
	db DB,
	storeTx RelationStoreTx[ID, RelatedID],
) RelationStore[ID, RelatedID]

type RelationStoreString

type RelationStoreString RelationStore[string, string]

type RelationStoreTx

type RelationStoreTx[ID ~[]byte | ~string, RelatedID ~[]byte | ~string] interface {
	// Add the given relationIDs to ID
	Add(ctx context.Context, tx Tx, id ID, relatedIDs []RelatedID) error
	// Replace all relations of id with the given
	Replace(ctx context.Context, tx Tx, id ID, relatedIDs []RelatedID) error
	// Remove all relation from ID to the given
	Remove(ctx context.Context, tx Tx, id ID, relatedIDs []RelatedID) error
	// Delete ID and all relations
	Delete(ctx context.Context, tx Tx, id ID) error
	// RelatedIDs return all relation of ID
	RelatedIDs(ctx context.Context, tx Tx, id ID) ([]RelatedID, error)
	// IDs return all ids of RelatedID
	IDs(ctx context.Context, tx Tx, relatedID RelatedID) ([]ID, error)
	// StreamIDs return all existing IDs
	StreamIDs(ctx context.Context, tx Tx, ch chan<- ID) error
	// StreamRelatedIDs return all existing relationIDs
	StreamRelatedIDs(ctx context.Context, tx Tx, ch chan<- RelatedID) error
	// Invert returns the same store with flipped ID <-> RelationID
	Invert() RelationStoreTx[RelatedID, ID]
	// MapIDRelations maps all entry to the given func
	MapIDRelations(
		ctx context.Context,
		tx Tx,
		fn func(ctx context.Context, key ID, relatedIDs []RelatedID) error,
	) error
	// MapRelationIDs maps all entry to the given func
	MapRelationIDs(
		ctx context.Context,
		tx Tx,
		fn func(ctx context.Context, key RelatedID, ids []ID) error,
	) error
}

func NewRelationStoreTx

func NewRelationStoreTx[ID ~[]byte | ~string, RelatedID ~[]byte | ~string](
	name string,
) RelationStoreTx[ID, RelatedID]

func NewRelationStoreTxWithBucket added in v1.12.0

func NewRelationStoreTxWithBucket[ID ~[]byte | ~string, RelatedID ~[]byte | ~string](
	idRelationBucket StoreTx[ID, []RelatedID],
	relationIDBucket StoreTx[RelatedID, []ID],
) RelationStoreTx[ID, RelatedID]

type RelationStoreTxString

type RelationStoreTxString RelationStoreTx[string, string]

type RunnableTx added in v1.14.0

type RunnableTx interface {
	Run(ctx context.Context, tx Tx) error
}

RunnableTx provides an interface for executing transaction logic.

type Store

type Store[KEY ~[]byte | ~string, OBJECT any] interface {
	StoreAdder[KEY, OBJECT]
	StoreRemover[KEY]
	StoreGetter[KEY, OBJECT]
	StoreMapper[KEY, OBJECT]
	StoreExists[KEY, OBJECT]
	StoreStreamer[KEY, OBJECT]
	StoreLister[KEY, OBJECT]
}

Store provides a complete type-safe key-value store interface combining all store operations.

func NewStore

func NewStore[KEY ~[]byte | ~string, OBJECT any](db DB, bucketName BucketName) Store[KEY, OBJECT]

NewStore returns a Store

func NewStoreFromTx added in v1.13.1

func NewStoreFromTx[KEY ~[]byte | ~string, OBJECT any](
	db DB,
	storeTx StoreTx[KEY, OBJECT],
) Store[KEY, OBJECT]

NewStoreFromTx returns a Store from a existing StoreTx

type StoreAdder

type StoreAdder[KEY ~[]byte | ~string, OBJECT any] interface {
	Add(ctx context.Context, key KEY, object OBJECT) error
}

StoreAdder provides functionality to add objects to a store.

type StoreAdderTx

type StoreAdderTx[KEY ~[]byte | ~string, OBJECT any] interface {
	Add(ctx context.Context, tx Tx, key KEY, object OBJECT) error
}

StoreAdderTx provides functionality to add objects within a transaction.

type StoreExists

type StoreExists[KEY ~[]byte | ~string, OBJECT any] interface {
	Exists(ctx context.Context, key KEY) (bool, error)
}

StoreExists provides functionality to check object existence in a store.

type StoreExistsTx

type StoreExistsTx[KEY ~[]byte | ~string, OBJECT any] interface {
	Exists(ctx context.Context, tx Tx, key KEY) (bool, error)
}

StoreExistsTx provides functionality to check object existence within a transaction.

type StoreGetter

type StoreGetter[KEY ~[]byte | ~string, OBJECT any] interface {
	Get(ctx context.Context, key KEY) (*OBJECT, error)
}

StoreGetter provides functionality to retrieve objects from a store.

type StoreGetterTx

type StoreGetterTx[KEY ~[]byte | ~string, OBJECT any] interface {
	Get(ctx context.Context, tx Tx, key KEY) (*OBJECT, error)
}

StoreGetterTx provides functionality to retrieve objects within a transaction.

type StoreList added in v1.15.0

type StoreList[KEY ~[]byte | ~string, OBJECT any] = StoreLister[KEY, OBJECT]

StoreList is deprecated: use StoreLister instead.

type StoreListTx added in v1.15.0

type StoreListTx[KEY ~[]byte | ~string, OBJECT any] = StoreListerTx[KEY, OBJECT]

StoreListTx is deprecated: use StoreListerTx instead.

type StoreLister added in v1.17.0

type StoreLister[KEY ~[]byte | ~string, OBJECT any] interface {
	List(ctx context.Context) ([]OBJECT, error)
}

StoreLister provides functionality to list all objects from a store.

type StoreListerTx added in v1.17.0

type StoreListerTx[KEY ~[]byte | ~string, OBJECT any] interface {
	List(ctx context.Context, tx Tx) ([]OBJECT, error)
}

StoreListerTx provides functionality to list all objects within a transaction.

type StoreMapper

type StoreMapper[KEY ~[]byte | ~string, OBJECT any] interface {
	Map(ctx context.Context, fn func(ctx context.Context, key KEY, object OBJECT) error) error
}

StoreMapper provides mapping functionality over all key-value pairs in a store.

type StoreMapperTx

type StoreMapperTx[KEY ~[]byte | ~string, OBJECT any] interface {
	Map(
		ctx context.Context,
		tx Tx,
		fn func(ctx context.Context, key KEY, object OBJECT) error,
	) error
}

StoreMapperTx provides mapping functionality over all key-value pairs within a transaction.

type StoreRemover

type StoreRemover[KEY ~[]byte | ~string] interface {
	Remove(ctx context.Context, key KEY) error
}

StoreRemover provides functionality to remove objects from a store.

type StoreRemoverTx

type StoreRemoverTx[KEY ~[]byte | ~string] interface {
	Remove(ctx context.Context, tx Tx, key KEY) error
}

StoreRemoverTx provides functionality to remove objects within a transaction.

type StoreStream

type StoreStream[KEY ~[]byte | ~string, OBJECT any] = StoreStreamer[KEY, OBJECT]

StoreStream is deprecated: use StoreStreamer instead.

type StoreStreamTx

type StoreStreamTx[KEY ~[]byte | ~string, OBJECT any] = StoreStreamerTx[KEY, OBJECT]

StoreStreamTx is deprecated: use StoreStreamerTx instead.

type StoreStreamer added in v1.17.0

type StoreStreamer[KEY ~[]byte | ~string, OBJECT any] interface {
	Stream(ctx context.Context, ch chan<- OBJECT) error
}

StoreStreamer provides functionality to stream all objects from a store.

type StoreStreamerTx added in v1.17.0

type StoreStreamerTx[KEY ~[]byte | ~string, OBJECT any] interface {
	Stream(ctx context.Context, tx Tx, ch chan<- OBJECT) error
}

StoreStreamerTx provides functionality to stream all objects within a transaction.

type StoreTx

type StoreTx[KEY ~[]byte | ~string, OBJECT any] interface {
	StoreAdderTx[KEY, OBJECT]
	StoreRemoverTx[KEY]
	StoreGetterTx[KEY, OBJECT]
	StoreMapperTx[KEY, OBJECT]
	StoreExistsTx[KEY, OBJECT]
	StoreStreamerTx[KEY, OBJECT]
	StoreListerTx[KEY, OBJECT]
}

StoreTx provides a complete type-safe key-value store interface for transaction-based operations.

func NewStoreTx

func NewStoreTx[KEY ~[]byte | ~string, OBJECT any](bucketName BucketName) StoreTx[KEY, OBJECT]

NewStoreTx creates a new type-safe transaction-based store for the specified bucket.

type Tx

type Tx interface {
	Bucket(ctx context.Context, name BucketName) (Bucket, error)
	CreateBucket(ctx context.Context, name BucketName) (Bucket, error)
	CreateBucketIfNotExists(ctx context.Context, name BucketName) (Bucket, error)
	DeleteBucket(ctx context.Context, name BucketName) error
	ListBucketNames(ctx context.Context) (BucketNames, error)
}

Tx represents a database transaction that provides bucket management operations.

Directories

Path Synopsis
Code generated by counterfeiter.
Code generated by counterfeiter.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL