Skip to content

NicoNex/katalis

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Katalis

Go Reference Go Report Card codecov

Katalis is a type-safe, generic wrapper around the Pogreb embedded key-value store for Go. It leverages Go generics to provide compile-time type safety while maintaining the performance and simplicity of Pogreb.

Why Katalis?

Traditional key-value stores in Go require manual serialization/deserialization of keys and values to []byte. This approach is:

  • Error-prone: Type mismatches discovered only at runtime
  • Verbose: Boilerplate code for encoding/decoding
  • Unsafe: Easy to store/retrieve wrong types

Katalis solves these issues through:

  • Type Safety: Compile-time guarantees for key-value types
  • Zero Boilerplate: Automatic encoding/decoding via codec system
  • Flexibility: Pluggable codecs for any type
  • Modern Go: Built-in support for Go 1.23+ iterators

Features

  • Type-Safe Operations: Use Go generics to enforce type safety for key-value pairs
  • Flexible Codec System: Predefined codecs for primitives, custom codecs for complex types
  • High Performance: Built on Pogreb's fast, embedded storage engine
  • Iterator Support: Native support for Go 1.23+ range-over-function pattern
  • Error Handling: Multiple iteration strategies for different error handling needs
  • Simple API: Clean, idiomatic Go interface

Installation

go get github.com/NicoNex/katalis@latest

Requires Go 1.23 or later.

Quick Start

package main

import (
	"fmt"
	"github.com/NicoNex/katalis"
)

func main() {
	// Open a type-safe database with string keys and int values
	db, err := katalis.Open("mydb", katalis.StringCodec, katalis.IntCodec)
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// Put and Get operations are type-safe
	db.Put("age", 42)          // Compile-time type checking
	age, _ := db.Get("age")    // Returns int, not interface{}

	fmt.Println(age) // Output: 42
}

Design Philosophy

Codec System

Katalis uses a codec pattern to handle serialization transparently. Each type needs a codec that implements:

type Codec[T any] interface {
    Encode(T) ([]byte, error)
    Decode([]byte) (T, error)
}

Predefined Codecs

Katalis provides codecs for all Go primitive types:

// Integer types
katalis.IntCodec, katalis.Int8Codec, katalis.Int16Codec, katalis.Int32Codec, katalis.Int64Codec
katalis.UintCodec, katalis.Uint8Codec, katalis.Uint16Codec, katalis.Uint32Codec, katalis.Uint64Codec

// Floating point
katalis.Float32Codec, katalis.Float64Codec

// Strings and bytes
katalis.StringCodec, katalis.BytesCodec

Custom Types with Gob

For structs and complex types, use Gob:

type User struct {
	Name string
	Age  int
}

// Option 1: Explicit type parameter
db, _ := katalis.Open("users.db", katalis.StringCodec, katalis.Gob[User]())

// Option 2: Type inference
var user User
db, _ := katalis.Open("users.db", katalis.StringCodec, katalis.Gob(user))

Iteration Methods

Katalis provides three iteration strategies, each with different tradeoffs:

1. Items() - Simple, Error-Skipping Iteration

Use when you want clean, simple iteration that automatically skips corrupted entries:

for key, value := range db.Items() {
	fmt.Printf("%s: %d\n", key, value)
	// Automatically skips entries that fail to decode
}

When to use: Default choice for most cases, when you want simplicity and resilience without explicit error handling.

2. AllItems() - Explicit Error Handling

Use when you need to know about and handle decode errors explicitly:

for entry, err := range db.AllItems() {
	if err != nil {
		log.Printf("Error decoding entry: %v", err)
		continue // or handle differently
	}
	fmt.Printf("%s: %d\n", entry.Key, entry.Value)
}

When to use: Production monitoring, debugging, when you need to log/report errors, or take specific action on failures.

3. Fold() - Callback-Based Iteration

Use when you need custom error handling or accumulation logic:

total := 0
err := db.Fold(func(key string, val int, err error) error {
	if err != nil {
		return err // Stop on error
	}
	total += val
	return nil
})

When to use: Aggregations, reductions, custom control flow.

Usage Examples

Basic CRUD Operations

db, _ := katalis.Open("db", katalis.StringCodec, katalis.IntCodec)
defer db.Close()

// Put
db.Put("score", 100)

// Get
score, err := db.Get("score")
if err != nil {
	// Handle error
}

// Has
exists, _ := db.Has("score")

// Delete
db.Del("score")

Working with Complex Types

type Article struct {
	Title   string
	Author  string
	Content string
	Tags    []string
}

db, _ := katalis.Open(
	"articles.db",
	katalis.StringCodec,      // Keys are strings
	katalis.Gob[Article](),   // Values are Articles
)
defer db.Close()

article := Article{
	Title:   "Understanding Katalis",
	Author:  "Alice",
	Content: "...",
	Tags:    []string{"go", "database"},
}

// Store
db.Put("article-1", article)

// Retrieve with full type safety
retrieved, _ := db.Get("article-1")
fmt.Println(retrieved.Title) // No type assertions needed!

Iteration Examples

// Simple iteration (automatically skips errors)
count := 0
for key, val := range db.Items() {
	count++
}

// Iteration with explicit error handling
for entry, err := range db.AllItems() {
	if err != nil {
		log.Printf("Corrupted entry: %v", err)
		continue
	}
	process(entry.Key, entry.Value)
}

// Early exit
for key, val := range db.Items() {
	if key == "target" {
		break // Stop iteration
	}
}

Error Handling Patterns

// Pattern 1: Strict error handling
for entry, err := range db.AllItems() {
	if err != nil {
		return fmt.Errorf("database corrupted: %w", err)
	}
	// Process entry.Key and entry.Value
}

// Pattern 2: Graceful degradation
var failed []string
for entry, err := range db.AllItems() {
	if err != nil {
		failed = append(failed, "unknown")
		continue
	}
	// Process valid entries
}

// Pattern 3: Fold with accumulation
err := db.Fold(func(key string, val int, err error) error {
	if err != nil {
		return err // Propagate error
	}
	// Process and accumulate
	return nil
})

API Reference

Database Operations

// Open database with codecs
Open[KT, VT any](path string, keyCodec Codec[KT], valCodec Codec[VT]) (DB[KT, VT], error)
OpenOptions[KT, VT any](path string, keyCodec Codec[KT], valCodec Codec[VT], opts *Options) (DB[KT, VT], error)

// CRUD operations
Put(key KT, val VT) error
Get(key KT) (VT, error)
Has(key KT) (bool, error)
Del(key KT) error

// Iteration
Items() iter.Seq2[KT, VT]                            // Simple, skips errors
AllItems() iter.Seq2[Entry[KT, VT], error]           // With error handling
Fold(fn func(key KT, val VT, err error) error) error // Callback-based

// Close
Close() error

Entry Type

type Entry[KT, VT any] struct {
	Key   KT
	Value VT
}

Used by AllItems() to return key-value pairs with error information.

Performance Considerations

  • Codec Choice: Primitive codecs (Int, String) are faster than Gob for simple types
  • Iteration Method: Items() and AllItems() have similar performance; use Items() for simplicity, AllItems() when you need error details
  • Database Options: Tune Pogreb options via OpenOptions for specific workloads

Contributing

Contributions are welcome! Please ensure:

  • Tests pass: go test ./...
  • Code is formatted: go fmt ./...
  • Changes are documented

License

See LICENSE file for details.

Acknowledgments

Built on top of Pogreb by Constantine Peresypkin.

About

Efficiently store go structs and values on disk.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages