From 52eb0bd62ae969d49705002ec0d41fce0e2e6b54 Mon Sep 17 00:00:00 2001 From: aryehklein-rise Date: Wed, 23 Jul 2025 10:27:18 +0300 Subject: [PATCH 1/2] add cache loader. --- README.md | 49 ++++++++++++++++++++++++++++++++++-- loader.go | 48 +++++++++++++++++++++++++++++++++++ loader_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 loader.go create mode 100644 loader_test.go diff --git a/README.md b/README.md index 7369cdf..f19aff9 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A simple and efficient cache implementation in Go that implements the S3FIFO (Si - Easy to understand and maintain - Minimal dependencies - **Generic Implementation**: Written in Go with generics support for type-safe caching of any comparable key type +- **Backend Loading Support**: Optional loader function to fetch values from a backend when not found in cache ## Installation @@ -20,12 +21,14 @@ go get github.com/aryehlev/cache-go ## Usage +### Basic Cache + ```go package main import ( "fmt" - + "github.com/aryehlev/cache-go" ) @@ -52,6 +55,49 @@ func main() { } ``` +### Cache with Backend Loader + +```go +package main + +import ( + "fmt" + "database/sql" + + "github.com/aryehlev/cache-go" +) + +func main() { + // Example database connection + db, err := sql.Open("mysql", "user:password@/dbname") + if err != nil { + panic(err) + } + defer db.Close() + + // Create a loader function that fetches values from the database + loader := func(key string) (int, bool) { + var value int + err := db.QueryRow("SELECT value FROM data WHERE key = ?", key).Scan(&value) + if err != nil { + return 0, false // Not found or error + } + return value, true + } + + // Create a new cache with size 1000 and the loader function + cache, err := cache_go.NewWithLoader[string, int](1000, loader) + if err != nil { + panic(err) + } + + // Get a value - will fetch from database if not in cache + if value, ok := cache.Get("key1"); ok { + fmt.Printf("Value: %d\n", value) + } +} +``` + ## How S3FIFO Works The S3FIFO algorithm divides the cache into three segments: @@ -71,4 +117,3 @@ visit the [official S3FIFO website](https://s3fifo.com/). ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. - diff --git a/loader.go b/loader.go new file mode 100644 index 0000000..ab08366 --- /dev/null +++ b/loader.go @@ -0,0 +1,48 @@ +package cache_go + +// LoaderFunc is a function type that loads a value for a key. +// It returns the loaded value and a boolean indicating whether the value was found. +type LoaderFunc[K comparable, V any] func(key K) (V, bool) + +// CacheWithLoader extends the Cache with a loader function that can fetch values from a backend. +type CacheWithLoader[K comparable, V any] struct { + *Cache[K, V] + loader LoaderFunc[K, V] +} + +// NewWithLoader creates a new cache with the specified size and loader function. +func NewWithLoader[K comparable, V any](size uint, loader LoaderFunc[K, V]) (*CacheWithLoader[K, V], error) { + cache, err := New[K, V](size) + if err != nil { + return nil, err + } + + return &CacheWithLoader[K, V]{ + Cache: cache, + loader: loader, + }, nil +} + +// Get retrieves a value from the cache. If the value is not in the cache, +// it attempts to load it using the loader function. +func (cl *CacheWithLoader[K, V]) Get(key K) (V, bool) { + // Try to get from cache first + value, found := cl.Cache.Get(key) + if found { + return value, true + } + + // If not in cache, try to load from backend + if cl.loader != nil { + value, found := cl.loader(key) + if found { + // Store the loaded value in the cache + cl.Cache.Set(key, value) + return value, true + } + } + + // Return zero value if not found + var zero V + return zero, false +} \ No newline at end of file diff --git a/loader_test.go b/loader_test.go new file mode 100644 index 0000000..06a271e --- /dev/null +++ b/loader_test.go @@ -0,0 +1,68 @@ +package cache_go + +import ( + "testing" +) + +func TestCacheWithLoader(t *testing.T) { + // Create a simple in-memory backend + backend := map[string]int{ + "key1": 42, + "key2": 84, + } + + // Create a loader function that fetches from the backend + loader := func(key string) (int, bool) { + value, found := backend[key] + return value, found + } + + // Create a cache with the loader + cache, err := NewWithLoader[string, int](100, loader) + if err != nil { + t.Fatalf("Failed to create cache: %v", err) + } + + // Test getting a value that's not in the cache but is in the backend + value, found := cache.Get("key1") + if !found { + t.Errorf("Expected to find key1 via loader, but it wasn't found") + } + if value != 42 { + t.Errorf("Expected value 42 for key1, got %d", value) + } + + // Test that the value is now cached + // We can verify this by checking if the value is still accessible + // even if we remove it from the backend + delete(backend, "key1") + value, found = cache.Get("key1") + if !found { + t.Errorf("Expected to find key1 in cache after loading, but it wasn't found") + } + if value != 42 { + t.Errorf("Expected value 42 for key1 from cache, got %d", value) + } + + // Test getting a value that's not in the cache or backend + value, found = cache.Get("nonexistent") + if found { + t.Errorf("Expected not to find nonexistent key, but it was found with value %d", value) + } + + // Test that the loader is not used when a value is already in the cache + // We'll set a value directly in the cache + cache.Set("key3", 123) + + // Then add a different value to the backend + backend["key3"] = 456 + + // When we get the value, it should come from the cache, not the backend + value, found = cache.Get("key3") + if !found { + t.Errorf("Expected to find key3 in cache, but it wasn't found") + } + if value != 123 { + t.Errorf("Expected value 123 for key3 from cache, got %d (should not have used loader)", value) + } +} \ No newline at end of file From 47faa5867032cff13237e20c091ebf709d036b91 Mon Sep 17 00:00:00 2001 From: aryehklein-rise Date: Wed, 23 Jul 2025 10:36:01 +0300 Subject: [PATCH 2/2] put loader before get to test ai. --- loader.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/loader.go b/loader.go index ab08366..26e41a9 100644 --- a/loader.go +++ b/loader.go @@ -26,11 +26,6 @@ func NewWithLoader[K comparable, V any](size uint, loader LoaderFunc[K, V]) (*Ca // Get retrieves a value from the cache. If the value is not in the cache, // it attempts to load it using the loader function. func (cl *CacheWithLoader[K, V]) Get(key K) (V, bool) { - // Try to get from cache first - value, found := cl.Cache.Get(key) - if found { - return value, true - } // If not in cache, try to load from backend if cl.loader != nil { @@ -42,6 +37,13 @@ func (cl *CacheWithLoader[K, V]) Get(key K) (V, bool) { } } + // Try to get from cache first + value, found := cl.Cache.Get(key) + if found { + return value, true + } + + // Return zero value if not found var zero V return zero, false