Repository: faabiosr/cachego Branch: main Commit: 859aca6d0873 Files: 38 Total size: 51.9 KB Directory structure: gitextract_vdd2xu1x/ ├── .github/ │ └── workflows/ │ └── test.yml ├── .gitignore ├── .golangci.yaml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── bolt/ │ ├── README.md │ ├── bolt.go │ └── bolt_test.go ├── cache.go ├── chain/ │ ├── README.md │ ├── chain.go │ └── chain_test.go ├── doc.go ├── docker-compose.yml ├── errors.go ├── errors_test.go ├── file/ │ ├── README.md │ ├── file.go │ └── file_test.go ├── go.mod ├── go.sum ├── memcached/ │ ├── README.md │ ├── memcached.go │ └── memcached_test.go ├── mongo/ │ ├── README.md │ ├── mongo.go │ └── mongo_test.go ├── redis/ │ ├── README.md │ ├── redis.go │ └── redis_test.go ├── sqlite3/ │ ├── README.md │ ├── sqlite3.go │ └── sqlite3_test.go └── sync/ ├── README.md ├── map.go └── map_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/test.yml ================================================ name: test on: push: branches: - main - develop pull_request: branches: - main jobs: test: strategy: matrix: go-version: - '1.21.x' - '1.22.x' - '1.23.x' platform: [ubuntu-latest] name: test runs-on: ${{ matrix.platform }} services: memcached: image: memcached:alpine ports: - 11211:11211 redis: image: redis:alpine ports: - 6379:6379 mongodb: image: mongo:3.6 ports: - 27017:27017 steps: - name: checkout the code uses: actions/checkout@v4 - name: setup go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: unshallow run: git fetch --prune --unshallow - name: golanci-linter uses: golangci/golangci-lint-action@v6 with: version: v1.64.7 - name: run unit tests run: make test - name: upload code coverage uses: codecov/codecov-action@v5 if: contains(github.ref, 'main') with: file: ./cover.out ================================================ FILE: .gitignore ================================================ *.swp *.swo cover* cache-dir ================================================ FILE: .golangci.yaml ================================================ --- run: timeout: "240s" output: formats: - format: "colored-line-number" linters: enable: - gocyclo - unconvert - goimports - unused - misspell - nakedret - errcheck - revive - ineffassign - goconst - govet - unparam - gofumpt - prealloc - mnd - gocritic linters-settings: revive: rules: - name: package-comments disabled: true issues: exclude-use-default: false ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing By participating to this project, you agree to abide our [code of conduct](/CODE_OF_CONDUCT.md). ## Setup your machine `cachego` is written in [Go](https://golang.org/). Prerequisites: * `make` * [Go 1.21+](https://golang.org/doc/install) Clone `cachego` from source into `$GOPATH`: ```sh $ mkdir -p $GOPATH/src/github.com/faabiosr $ cd $_ $ git clone git@github.com:faabiosr/cachego.git $ cd cachego ``` Install the build and lint dependencies: ```console $ make depend ``` A good way of making sure everything is all right is running the test suite: ```console $ make test ``` ## Formatting the code Format the code running: ```console $ make fmt ``` ## Create a commit Commit messages should be well formatted. Start your commit message with the type. Choose one of the following: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `revert`, `add`, `remove`, `move`, `bump`, `update`, `release` After a colon, you should give the message a title, starting with uppercase and ending without a dot. Keep the width of the text at 72 chars. The title must be followed with a newline, then a more detailed description. Please reference any GitHub issues on the last line of the commit message (e.g. `See #123`, `Closes #123`, `Fixes #123`). An example: ``` docs: Add example for --release-notes flag I added an example to the docs of the `--release-notes` flag to make the usage more clear. The example is an realistic use case and might help others to generate their own changelog. See #284 ``` ## Submit a pull request Push your branch to your `cachego` fork and open a pull request against the main branch. ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Fábio da Silva Ribeiro Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ .DEFAULT_GOAL := test # Clean up clean: @rm -fR ./vendor/ ./cover.* .PHONY: clean # Run tests and generates html coverage file cover: test @go tool cover -html=./cover.text -o ./cover.html @test -f ./cover.out && rm ./cover.out; .PHONY: cover # Up the docker container for testing docker: @docker-compose up -d .PHONY: docker # Format all go files fmt: @gofmt -s -w -l $(shell go list -f {{.Dir}} ./...) .PHONY: fmt # Run linters lint: @golangci-lint run ./... .PHONY: lint # Run tests test: @go test -v -race -coverprofile=./cover.text -covermode=atomic $(shell go list ./...) .PHONY: test ================================================ FILE: README.md ================================================ # Cachego [![Codecov branch](https://img.shields.io/codecov/c/github/faabiosr/cachego/main.svg?style=flat-square)](https://codecov.io/gh/faabiosr/cachego) [![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](https://pkg.go.dev/github.com/faabiosr/cachego) [![Go Report Card](https://goreportcard.com/badge/github.com/faabiosr/cachego?style=flat-square)](https://goreportcard.com/report/github.com/faabiosr/cachego) [![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/faabiosr/cachego/blob/main/LICENSE) Simple interface for caching ## Installation Cachego requires Go 1.21 or later. ``` go get github.com/faabiosr/cachego ``` ## Usage ```go package main import ( "log" "time" "github.com/faabiosr/cachego/sync" ) func main() { cache := sync.New() if err := cache.Save("user_id", "1", 10*time.Second); err != nil { log.Fatal(err) } id, err := cache.Fetch("user_id") if err != nil { log.Fatal(err) } log.Printf("user id: %s \n", id) keys := cache.FetchMulti([]string{"user_id", "user_name"}) for k, v := range keys { log.Printf("%s: %s\n", k, v) } if cache.Contains("user_name") { cache.Delete("user_name") } if _, err := cache.Fetch("user_name"); err != nil { log.Printf("%v\n", err) } if err := cache.Flush(); err != nil { log.Fatal(err) } } ``` ## Supported drivers - [Bolt](/bolt) - [Chain](/chain) - [File](/file) - [Memcached](/memcached) - [Mongo](/mongo) - [Redis](/redis) - [Sqlite3](/sqlite3) - [Sync](/sync) ## Documentation Read the full documentation at [https://pkg.go.dev/github.com/faabiosr/cachego](https://pkg.go.dev/github.com/faabiosr/cachego). ## Development ### Requirements - Install [docker](https://docs.docker.com/install/) - Install [docker-compose](https://docs.docker.com/compose/install/) ### Makefile ```sh // Clean up $ make clean //Run tests and generates html coverage file $ make cover // Up the docker containers for testing $ make docker // Format all go files $ make fmt //Run linters $ make lint // Run tests $ make test ``` ## License This project is released under the MIT licence. See [LICENSE](https://github.com/faabiosr/cachego/blob/main/LICENSE) for more details. ================================================ FILE: bolt/README.md ================================================ # Cachego - BoltDB driver The drivers uses [etcd-io/bbolt](https://github.com/etcd-io/bbolt) to store the cache data. ## Usage ```go package main import ( "log" "time" bt "go.etcd.io/bbolt" "github.com/faabiosr/cachego/bolt" ) func main() { db, err := bt.Open("cache.db", 0600, nil) if err != nil { log.Fatal(err) } cache := bolt.New(db) if err := cache.Save("user_id", "1", 10*time.Second); err != nil { log.Fatal(err) } id, err := cache.Fetch("user_id") if err != nil { log.Fatal(err) } log.Printf("user id: %s \n", id) } ``` ================================================ FILE: bolt/bolt.go ================================================ // Package bolt providers a cache driver that stores the cache using BoltDB. package bolt import ( "encoding/json" "errors" "time" bt "go.etcd.io/bbolt" "github.com/faabiosr/cachego" ) var boltBucket = []byte("cachego") type ( bolt struct { db *bt.DB } boltContent struct { Duration int64 `json:"duration"` Data string `json:"data,omitempty"` } ) // New creates an instance of BoltDB cache func New(db *bt.DB) cachego.Cache { return &bolt{db} } func (b *bolt) read(key string) (*boltContent, error) { var value []byte err := b.db.View(func(tx *bt.Tx) error { if bucket := tx.Bucket(boltBucket); bucket != nil { value = bucket.Get([]byte(key)) return nil } return errors.New("bucket not found") }) if err != nil { return nil, err } content := &boltContent{} if err := json.Unmarshal(value, content); err != nil { return nil, err } if content.Duration == 0 { return content, nil } if content.Duration <= time.Now().Unix() { _ = b.Delete(key) return nil, cachego.ErrCacheExpired } return content, nil } // Contains checks if the cached key exists into the BoltDB storage func (b *bolt) Contains(key string) bool { _, err := b.read(key) return err == nil } // Delete the cached key from BoltDB storage func (b *bolt) Delete(key string) error { return b.db.Update(func(tx *bt.Tx) error { if bucket := tx.Bucket(boltBucket); bucket != nil { return bucket.Delete([]byte(key)) } return errors.New("bucket not found") }) } // Fetch retrieves the cached value from key of the BoltDB storage func (b *bolt) Fetch(key string) (string, error) { content, err := b.read(key) if err != nil { return "", err } return content.Data, nil } // FetchMulti retrieve multiple cached values from keys of the BoltDB storage func (b *bolt) FetchMulti(keys []string) map[string]string { result := make(map[string]string) for _, key := range keys { if value, err := b.Fetch(key); err == nil { result[key] = value } } return result } // Flush removes all cached keys of the BoltDB storage func (b *bolt) Flush() error { return b.db.Update(func(tx *bt.Tx) error { return tx.DeleteBucket(boltBucket) }) } // Save a value in BoltDB storage by key func (b *bolt) Save(key string, value string, lifeTime time.Duration) error { duration := int64(0) if lifeTime > 0 { duration = time.Now().Unix() + int64(lifeTime.Seconds()) } content := &boltContent{duration, value} data, err := json.Marshal(content) if err != nil { return err } return b.db.Update(func(tx *bt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(boltBucket) if err != nil { return err } return bucket.Put([]byte(key), data) }) } ================================================ FILE: bolt/bolt_test.go ================================================ package bolt import ( "fmt" "os" "testing" "time" bt "go.etcd.io/bbolt" ) const ( testKey = "foo" testValue = "bar" ) func TestBolt(t *testing.T) { dir, err := os.MkdirTemp("", t.Name()) if err != nil { t.Fatal(err) } db, err := bt.Open(fmt.Sprintf("%s/cachego.db", dir), 0o600, nil) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = db.Close() _ = os.RemoveAll(dir) }) c := New(db) if err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil { t.Errorf("save fail: expected nil, got %v", err) } if _, err := c.Fetch(testKey); err == nil { t.Errorf("fetch fail: expected an error, got %v", err) } _ = c.Save(testKey, testValue, 10*time.Second) if res, _ := c.Fetch(testKey); res != testValue { t.Errorf("fetch fail, wrong value: expected %s, got %s", testValue, res) } _ = c.Save(testKey, testValue, 0) if !c.Contains(testKey) { t.Errorf("contains failed: the key %s should be exist", testKey) } _ = c.Save("bar", testValue, 0) if values := c.FetchMulti([]string{testKey, "bar"}); len(values) == 0 { t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values)) } if err := c.Flush(); err != nil { t.Errorf("flush failed: expected nil, got %v", err) } if err := c.Flush(); err == nil { t.Errorf("flush failed: expected error, got %v", err) } if err := c.Delete(testKey); err == nil { t.Errorf("delete failed: expected error, got %v", err) } if c.Contains(testKey) { t.Errorf("contains failed: the key %s should not be exist", testKey) } } ================================================ FILE: cache.go ================================================ package cachego import ( "time" ) type ( // Cache is the top-level cache interface Cache interface { // Contains check if a cached key exists Contains(key string) bool // Delete remove the cached key Delete(key string) error // Fetch retrieve the cached key value Fetch(key string) (string, error) // FetchMulti retrieve multiple cached keys value FetchMulti(keys []string) map[string]string // Flush remove all cached keys Flush() error // Save cache a value by key Save(key string, value string, lifeTime time.Duration) error } ) ================================================ FILE: chain/README.md ================================================ # Cachego - Chain driver The chain driver deals with multiple driver at same time, it could save the key in multiple drivers and for fetching the driver will call the first one, if fails it will try the next until fail. ## Usage ```go package main import ( "log" "time" bt "go.etcd.io/bbolt" "github.com/faabiosr/cachego/bolt" "github.com/faabiosr/cachego/chain" "github.com/faabiosr/cachego/sync" ) func main() { db, err := bt.Open("cache.db", 0600, nil) if err != nil { log.Fatal(err) } cache := chain.New( bolt.New(db), sync.New(), ) if err := cache.Save("user_id", "1", 10*time.Second); err != nil { log.Fatal(err) } id, err := cache.Fetch("user_id") if err != nil { log.Fatal(err) } log.Printf("user id: %s \n", id) } ``` ================================================ FILE: chain/chain.go ================================================ // Package chain provides chaining cache drivers operations, in case of failure // the driver try to apply using the next driver informed, until fail. package chain import ( "errors" "time" "github.com/faabiosr/cachego" ) type ( chain struct { drivers []cachego.Cache } ) // New creates an instance of Chain cache driver func New(drivers ...cachego.Cache) cachego.Cache { return &chain{drivers} } // Contains checks if the cached key exists in one of the cache storages func (c *chain) Contains(key string) bool { for _, driver := range c.drivers { if driver.Contains(key) { return true } } return false } // Delete the cached key in all cache storages func (c *chain) Delete(key string) error { for _, driver := range c.drivers { if err := driver.Delete(key); err != nil { return err } } return nil } // Fetch retrieves the value of one of the registred cache storages func (c *chain) Fetch(key string) (string, error) { for _, driver := range c.drivers { value, err := driver.Fetch(key) if err == nil { return value, nil } } return "", errors.New("key not found in cache chain") } // FetchMulti retrieves multiple cached values from one of the registred cache storages func (c *chain) FetchMulti(keys []string) map[string]string { result := make(map[string]string) for _, key := range keys { if value, err := c.Fetch(key); err == nil { result[key] = value } } return result } // Flush removes all cached keys of the registered cache storages func (c *chain) Flush() error { for _, driver := range c.drivers { if err := driver.Flush(); err != nil { return err } } return nil } // Save a value in all cache storages by key func (c *chain) Save(key string, value string, lifeTime time.Duration) error { for _, driver := range c.drivers { if err := driver.Save(key, value, lifeTime); err != nil { return err } } return nil } ================================================ FILE: chain/chain_test.go ================================================ package chain import ( "testing" "time" "github.com/bradfitz/gomemcache/memcache" "github.com/faabiosr/cachego/memcached" "github.com/faabiosr/cachego/sync" ) const ( testKey = "foo" testValue = "bar" ) func TestChain(t *testing.T) { c := New(sync.New()) if err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil { t.Errorf("save fail: expected nil, got %v", err) } if _, err := c.Fetch(testKey); err == nil { t.Errorf("fetch fail: expected an error, got %v", err) } _ = c.Save(testKey, testValue, 10*time.Second) if res, _ := c.Fetch(testKey); res != testValue { t.Errorf("fetch fail, wrong value: expected %s, got %s", testValue, res) } _ = c.Save(testKey, testValue, 0) if !c.Contains(testKey) { t.Errorf("contains failed: the key %s should be exist", testKey) } _ = c.Save("bar", testValue, 0) if values := c.FetchMulti([]string{testKey, "bar"}); len(values) == 0 { t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values)) } if err := c.Delete(testKey); err != nil { t.Errorf("delete failed: expected nil, got %v", err) } if err := c.Flush(); err != nil { t.Errorf("flush failed: expected nil, got %v", err) } if c.Contains(testKey) { t.Errorf("contains failed: the key %s should not be exist", testKey) } c = New( sync.New(), memcached.New(memcache.New("127.0.0.1:22222")), ) if err := c.Save(testKey, testValue, 0); err == nil { t.Errorf("save failed: expected an error, got %v", err) } if err := c.Delete(testKey); err == nil { t.Errorf("delete failed: expected an error, got %v", err) } if err := c.Flush(); err == nil { t.Errorf("flush failed: expected an error, got %v", err) } } ================================================ FILE: doc.go ================================================ // Package cachego provides a simple way to use cache drivers. // // # Example Usage // // The following is a simple example using memcached driver: // // import ( // "fmt" // "github.com/faabiosr/cachego" // "github.com/bradfitz/gomemcache/memcache" // ) // // func main() { // // cache := cachego.NewMemcached( // memcached.New("localhost:11211"), // ) // // cache.Save("foo", "bar") // // fmt.Println(cache.Fetch("foo")) // } package cachego ================================================ FILE: docker-compose.yml ================================================ --- services: memcached: image: "memcached:alpine" ports: - "11211:11211" redis: image: "redis:alpine" ports: - "6379:6379" mongodb: image: "mongo:3.6" ports: - "27017:27017" ================================================ FILE: errors.go ================================================ package cachego type err string // Error returns the string error value. func (e err) Error() string { return string(e) } const ( // ErrCacheExpired returns an error when the cache key was expired. ErrCacheExpired = err("cache expired") // ErrFlush returns an error when flush fails. ErrFlush = err("unable to flush") // ErrSave returns an error when save fails. ErrSave = err("unable to save") // ErrDelete returns an error when deletion fails. ErrDelete = err("unable to delete") // ErrDecode returns an errors when decode fails. ErrDecode = err("unable to decode") ) ================================================ FILE: errors_test.go ================================================ package cachego import ( "fmt" "testing" ) func TestError(t *testing.T) { expect := "failed" er := err(expect) if r := fmt.Sprint(er); r != expect { t.Errorf("invalid string: expect %s, got %s", expect, r) } } ================================================ FILE: file/README.md ================================================ # Cachego - File driver The driver stores the cache data in file. ## Usage ```go package main import ( "log" "time" "github.com/faabiosr/cachego/file" ) func main() { cache := file.New("./cache-dir/") if err := cache.Save("user_id", "1", 10*time.Second); err != nil { log.Fatal(err) } id, err := cache.Fetch("user_id") if err != nil { log.Fatal(err) } log.Printf("user id: %s \n", id) } ``` ================================================ FILE: file/file.go ================================================ // Package file providers a cache driver that stores the cache content in files. package file import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "os" "path/filepath" "sync" "time" "github.com/faabiosr/cachego" ) type ( file struct { dir string sync.RWMutex } fileContent struct { Duration int64 `json:"duration"` Data string `json:"data,omitempty"` } ) const perm = 0o666 // New creates an instance of File cache func New(dir string) cachego.Cache { return &file{dir: dir} } func (f *file) createName(key string) string { h := sha256.New() _, _ = h.Write([]byte(key)) hash := hex.EncodeToString(h.Sum(nil)) return filepath.Join(f.dir, fmt.Sprintf("%s.cachego", hash)) } func (f *file) read(key string) (*fileContent, error) { f.RLock() defer f.RUnlock() value, err := os.ReadFile(f.createName(key)) if err != nil { return nil, err } content := &fileContent{} if err := json.Unmarshal(value, content); err != nil { return nil, err } if content.Duration == 0 { return content, nil } return content, nil } // Contains checks if the cached key exists into the File storage func (f *file) Contains(key string) bool { content, err := f.read(key) if err != nil { return false } if f.isExpired(content) { _ = f.Delete(key) return false } return true } // Delete the cached key from File storage func (f *file) Delete(key string) error { f.Lock() defer f.Unlock() _, err := os.Stat(f.createName(key)) if err != nil && os.IsNotExist(err) { return nil } return os.Remove(f.createName(key)) } // Fetch retrieves the cached value from key of the File storage func (f *file) Fetch(key string) (string, error) { content, err := f.read(key) if err != nil { return "", err } if f.isExpired(content) { _ = f.Delete(key) return "", cachego.ErrCacheExpired } return content.Data, nil } func (f *file) isExpired(content *fileContent) bool { return content.Duration > 0 && content.Duration <= time.Now().Unix() } // FetchMulti retrieve multiple cached values from keys of the File storage func (f *file) FetchMulti(keys []string) map[string]string { result := make(map[string]string) for _, key := range keys { if value, err := f.Fetch(key); err == nil { result[key] = value } } return result } // Flush removes all cached keys of the File storage func (f *file) Flush() error { f.Lock() defer f.Unlock() dir, err := os.Open(f.dir) if err != nil { return err } defer func() { _ = dir.Close() }() names, _ := dir.Readdirnames(-1) for _, name := range names { _ = os.Remove(filepath.Join(f.dir, name)) } return nil } // Save a value in File storage by key func (f *file) Save(key string, value string, lifeTime time.Duration) error { f.Lock() defer f.Unlock() duration := int64(0) if lifeTime > 0 { duration = time.Now().Unix() + int64(lifeTime.Seconds()) } content := &fileContent{duration, value} data, err := json.Marshal(content) if err != nil { return err } return os.WriteFile(f.createName(key), data, perm) } ================================================ FILE: file/file_test.go ================================================ package file import ( "os" "testing" "time" ) const ( testKey = "foo" testValue = "bar" ) func TestFile(t *testing.T) { dir, err := os.MkdirTemp("", t.Name()) if err != nil { t.Fatal(err) } c := New(dir) if err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil { t.Errorf("save fail: expected nil, got %v", err) } if _, err := c.Fetch(testKey); err == nil { t.Errorf("fetch fail: expected an error, got %v", err) } _ = c.Save(testKey, testValue, 10*time.Second) if res, _ := c.Fetch(testKey); res != testValue { t.Errorf("fetch fail, wrong value: expected %s, got %s", testValue, res) } _ = c.Save(testKey, testValue, 0) if !c.Contains(testKey) { t.Errorf("contains failed: the key %s should be exist", testKey) } _ = c.Save("bar", testValue, 0) if values := c.FetchMulti([]string{testKey, "bar"}); len(values) == 0 { t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values)) } if err := c.Flush(); err != nil { t.Errorf("flush failed: expected nil, got %v", err) } if c.Contains(testKey) { t.Errorf("contains failed: the key %s should not be exist", testKey) } c = New("./test/") if err := c.Save(testKey, testValue, 0); err == nil { t.Errorf("save failed: expected an error, got %v", err) } if _, err := c.Fetch(testKey); err == nil { t.Errorf("fetch failed: expected and error, got %v", err) } if err := c.Flush(); err == nil { t.Errorf("flush failed: expected an error, got %v", err) } } ================================================ FILE: go.mod ================================================ module github.com/faabiosr/cachego go 1.21 require ( github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 github.com/mattn/go-sqlite3 v1.14.24 github.com/redis/go-redis/v9 v9.7.3 go.etcd.io/bbolt v1.3.10 go.mongodb.org/mongo-driver/v2 v2.0.1 ) require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/golang/snappy v0.0.4 // indirect github.com/klauspost/compress v1.16.7 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/crypto v0.33.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect ) ================================================ FILE: go.sum ================================================ github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= go.mongodb.org/mongo-driver/v2 v2.0.1 h1:mhB/ZJkLSv6W6LGzY7sEjpZif47+JdfEEXjlLCIv7Qc= go.mongodb.org/mongo-driver/v2 v2.0.1/go.mod h1:w7iFnTcQDMXtdXwcvyG3xljYpoBa1ErkI0yOzbkZ9b8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: memcached/README.md ================================================ # Cachego - Memcached driver The drivers uses [gomemcache](https://github.com/bradfitz/gomemcache) to store the cache data. ## Usage ```go package main import ( "log" "time" "github.com/bradfitz/gomemcache/memcache" "github.com/faabiosr/cachego/memcached" ) func main() { cache := memcached.New( memcache.New("localhost:11211"), ) if err := cache.Save("user_id", "1", 10*time.Second); err != nil { log.Fatal(err) } id, err := cache.Fetch("user_id") if err != nil { log.Fatal(err) } log.Printf("user id: %s \n", id) } ``` ================================================ FILE: memcached/memcached.go ================================================ // Package memcached providers a cache driver that stores the cache in Memcached. package memcached import ( "time" "github.com/bradfitz/gomemcache/memcache" "github.com/faabiosr/cachego" ) type memcached struct { driver *memcache.Client } // New creates an instance of Memcached cache driver func New(driver *memcache.Client) cachego.Cache { return &memcached{driver} } // Contains checks if cached key exists in Memcached storage func (m *memcached) Contains(key string) bool { _, err := m.Fetch(key) return err == nil } // Delete the cached key from Memcached storage func (m *memcached) Delete(key string) error { return m.driver.Delete(key) } // Fetch retrieves the cached value from key of the Memcached storage func (m *memcached) Fetch(key string) (string, error) { item, err := m.driver.Get(key) if err != nil { return "", err } return string(item.Value), nil } // FetchMulti retrieves multiple cached value from keys of the Memcached storage func (m *memcached) FetchMulti(keys []string) map[string]string { result := make(map[string]string) items, err := m.driver.GetMulti(keys) if err != nil { return result } for _, i := range items { result[i.Key] = string(i.Value) } return result } // Flush removes all cached keys of the Memcached storage func (m *memcached) Flush() error { return m.driver.FlushAll() } // Save a value in Memcached storage by key func (m *memcached) Save(key string, value string, lifeTime time.Duration) error { return m.driver.Set( &memcache.Item{ Key: key, Value: []byte(value), Expiration: int32(lifeTime.Seconds()), }, ) } ================================================ FILE: memcached/memcached_test.go ================================================ package memcached import ( "net" "testing" "time" "github.com/bradfitz/gomemcache/memcache" ) const ( testKey = "foo" testValue = "bar" ) func TestMemcached(t *testing.T) { address := "localhost:11211" conn := memcache.New(address) if _, err := net.Dial("tcp", address); err != nil { t.Skip(err) } c := New(conn) if err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil { t.Errorf("save fail: expected nil, got %v", err) } if _, err := c.Fetch("bar"); err == nil { t.Errorf("fetch fail: expected an error, got %v", err) } _ = c.Save(testKey, testValue, 10*time.Second) if res, _ := c.Fetch(testKey); res != testValue { t.Errorf("fetch fail, wrong value: expected %s, got %s", testValue, res) } _ = c.Save(testKey, testValue, 0) if !c.Contains(testKey) { t.Errorf("contains failed: the key %s should be exist", testKey) } _ = c.Save("bar", testValue, 0) if values := c.FetchMulti([]string{testKey, "bar"}); len(values) == 0 { t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values)) } if err := c.Flush(); err != nil { t.Errorf("flush failed: expected nil, got %v", err) } if c.Contains(testKey) { t.Errorf("contains failed: the key %s should not be exist", testKey) } c = New(memcache.New("localhost:22222")) if values := c.FetchMulti([]string{testKey, "bar"}); len(values) != 0 { t.Errorf("fetch multi failed: expected %d, got %d", 0, len(values)) } } ================================================ FILE: mongo/README.md ================================================ # Cachego - Mongo driver The drivers uses [go-mgo](https://github.com/go-mgo/mgo) to store the cache data. ## Usage ```go package main import ( "context" "log" "time" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "github.com/faabiosr/cachego/mongo" ) func main() { opts := options.Client().ApplyURI("mongodb://localhost:27017") client, _ := mongo.Connect(opts) cache := mongo.New( client.Database("cache").Collection("cache"), ) if err := cache.Save("user_id", "1", 10*time.Second); err != nil { log.Fatal(err) } id, err := cache.Fetch("user_id") if err != nil { log.Fatal(err) } log.Printf("user id: %s \n", id) } ``` ================================================ FILE: mongo/mongo.go ================================================ // Package mongo providers a cache driver that stores the cache in MongoDB. package mongo import ( "context" "time" "github.com/faabiosr/cachego" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) type ( mongoCache struct { collection *mongo.Collection } mongoContent struct { Duration int64 Key string `bson:"_id"` Value string } ) // New creates an instance of Mongo cache driver func New(collection *mongo.Collection) cachego.Cache { return &mongoCache{collection} } // NewMongoDriver alias for New. func NewMongoDriver(collection *mongo.Collection) cachego.Cache { return New(collection) } func (m *mongoCache) Contains(key string) bool { _, err := m.Fetch(key) return err == nil } // Delete the cached key from Mongo storage func (m *mongoCache) Delete(key string) error { _, err := m.collection.DeleteOne(context.TODO(), bson.M{"_id": bson.M{"$eq": key}}) return err } // Fetch retrieves the cached value from key of the Mongo storage func (m *mongoCache) Fetch(key string) (string, error) { content := &mongoContent{} result := m.collection.FindOne(context.TODO(), bson.M{"_id": bson.M{"$eq": key}}) if result == nil { return "", cachego.ErrCacheExpired } if result.Err() != nil { return "", result.Err() } err := result.Decode(&content) if err != nil { return "", err } if content.Duration == 0 { return content.Value, nil } if content.Duration <= time.Now().Unix() { _ = m.Delete(key) return "", cachego.ErrCacheExpired } return content.Value, nil } func (m *mongoCache) FetchMulti(keys []string) map[string]string { result := make(map[string]string) cur, err := m.collection.Find(context.TODO(), bson.M{"_id": bson.M{"$in": keys}}) if err != nil { return result } defer func() { _ = cur.Close(context.Background()) }() content := &mongoContent{} for cur.Next(context.Background()) { err := cur.Decode(content) if err != nil { continue } result[content.Key] = content.Value } return result } // Flush removes all cached keys of the Mongo storage func (m *mongoCache) Flush() error { _, err := m.collection.DeleteMany(context.TODO(), bson.M{}) return err } // Save a value in Mongo storage by key func (m *mongoCache) Save(key string, value string, lifeTime time.Duration) error { duration := int64(0) if lifeTime > 0 { duration = time.Now().Unix() + int64(lifeTime.Seconds()) } content := &mongoContent{duration, key, value} opts := options.Replace().SetUpsert(true) _, err := m.collection.ReplaceOne(context.TODO(), bson.M{"_id": bson.M{"$eq": key}}, content, opts) return err } ================================================ FILE: mongo/mongo_test.go ================================================ package mongo import ( "fmt" "testing" "time" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) const ( testKeyMongo = "foo1" testValueMongo = "bar" ) func TestMongo(t *testing.T) { // Set client options clientOptions := options.Client().ApplyURI("mongodb://localhost:27017") // Connect to MongoDB client, err := mongo.Connect(clientOptions) if err != nil { t.Skip(err) } collection := client.Database("cache").Collection("cache") cache := New(collection) if err := cache.Save(testKeyMongo, testValueMongo, 1*time.Nanosecond); err != nil { t.Errorf("save fail: expected nil, got %v", err) } if v, err := cache.Fetch(testKeyMongo); err == nil { t.Errorf("fetch fail: expected an error, got %v value %v", err, v) } _ = cache.Save(testKeyMongo, testValueMongo, 10*time.Second) if res, _ := cache.Fetch(testKeyMongo); res != testValueMongo { t.Errorf("fetch fail, wrong value : expected %s, got %s", testValueMongo, res) } _ = cache.Save(testKeyMongo, testValueMongo, 0) if !cache.Contains(testKeyMongo) { t.Errorf("contains failed: the key %s should be exist", testKeyMongo) } _ = cache.Save("bar", testValueMongo, 0) if values := cache.FetchMulti([]string{testKeyMongo, "bar"}); len(values) != 2 { fmt.Println(values) t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values)) } if err := cache.Flush(); err != nil { t.Errorf("flush failed: expected nil, got %v", err) } if cache.Contains(testKeyMongo) { t.Errorf("contains failed: the key %s should not be exist", testKeyMongo) } } ================================================ FILE: redis/README.md ================================================ # Cachego - Redis driver The drivers uses [go-redis](https://github.com/go-redis/redis) to store the cache data. ## Usage ```go package main import ( "log" "time" rd "github.com/redis/go-redis/v9" "github.com/faabiosr/cachego/redis" ) func main() { cache := redis.New( rd.NewClient(&rd.Options{ Addr: ":6379", }), ) if err := cache.Save("user_id", "1", 10*time.Second); err != nil { log.Fatal(err) } id, err := cache.Fetch("user_id") if err != nil { log.Fatal(err) } log.Printf("user id: %s \n", id) } ``` ================================================ FILE: redis/redis.go ================================================ // Package redis providers a cache driver that stores the cache in Redis. package redis import ( "context" "time" rd "github.com/redis/go-redis/v9" "github.com/faabiosr/cachego" ) type redis struct { driver rd.Cmdable } // New creates an instance of Redis cache driver func New(driver rd.Cmdable) cachego.Cache { return &redis{driver} } // Contains checks if cached key exists in Redis storage func (r *redis) Contains(key string) bool { i, _ := r.driver.Exists(context.Background(), key).Result() return i > 0 } // Delete the cached key from Redis storage func (r *redis) Delete(key string) error { return r.driver.Del(context.Background(), key).Err() } // Fetch retrieves the cached value from key of the Redis storage func (r *redis) Fetch(key string) (string, error) { return r.driver.Get(context.Background(), key).Result() } // FetchMulti retrieves multiple cached value from keys of the Redis storage func (r *redis) FetchMulti(keys []string) map[string]string { result := make(map[string]string) items, err := r.driver.MGet(context.Background(), keys...).Result() if err != nil { return result } for i := 0; i < len(keys); i++ { if items[i] != nil { result[keys[i]] = items[i].(string) } } return result } // Flush removes all cached keys of the Redis storage func (r *redis) Flush() error { return r.driver.FlushAll(context.Background()).Err() } // Save a value in Redis storage by key func (r *redis) Save(key string, value string, lifeTime time.Duration) error { return r.driver.Set(context.Background(), key, value, lifeTime).Err() } ================================================ FILE: redis/redis_test.go ================================================ package redis import ( "net" "testing" "time" rd "github.com/redis/go-redis/v9" ) const ( testKey = "foo" testValue = "bar" ) func TestRedis(t *testing.T) { conn := rd.NewClient(&rd.Options{ Addr: ":6379", }) if _, err := net.Dial("tcp", "localhost:6379"); err != nil { t.Skip(err) } c := New(conn) if err := c.Save(testKey, testValue, 10*time.Second); err != nil { t.Errorf("save fail: expected nil, got %v", err) } if res, _ := c.Fetch(testKey); res != testValue { t.Errorf("fetch fail, wrong value: expected %s, got %s", testValue, res) } if _, err := c.Fetch("bar"); err == nil { t.Errorf("fetch fail: expected an error, got %v", err) } if !c.Contains(testKey) { t.Errorf("contains failed: the key %s should be exist", testKey) } _ = c.Save("bar", testValue, 0) if values := c.FetchMulti([]string{testKey, "bar"}); len(values) == 0 { t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values)) } if err := c.Delete(testKey); err != nil { t.Errorf("delete failed: expected nil, got %v", err) } if err := c.Flush(); err != nil { t.Errorf("flush failed: expected nil, got %v", err) } if c.Contains(testKey) { t.Errorf("contains failed: the key %s should not be exist", testKey) } conn = rd.NewClient(&rd.Options{Addr: ":6380"}) c = New(conn) if c.Contains(testKey) { t.Errorf("contains failed: the key %s should not be exist", testKey) } if values := c.FetchMulti([]string{testKey, "bar"}); len(values) != 0 { t.Errorf("fetch multi failed: expected %d, got %d", 0, len(values)) } } ================================================ FILE: sqlite3/README.md ================================================ # Cachego - SQLite3 driver The drivers uses [go-sqlite3](https://github.com/mattn/go-sqlite3) to store the cache data. ## Usage ```go package main import ( "database/sql" "log" "time" _ "github.com/mattn/go-sqlite3" "github.com/faabiosr/cachego/sqlite3" ) func main() { db, err := sql.Open("sqlite3", "./cache.db") if err != nil { log.Fatal(err) } cache, err := sqlite3.New(db, "cache") if err != nil { log.Fatal(err) } if err := cache.Save("user_id", "1", 10*time.Second); err != nil { log.Fatal(err) } id, err := cache.Fetch("user_id") if err != nil { log.Fatal(err) } log.Printf("user id: %s \n", id) } ``` ================================================ FILE: sqlite3/sqlite3.go ================================================ // Package sqlite3 providers a cache driver that stores the cache in SQLite3. package sqlite3 import ( "database/sql" "fmt" "time" "github.com/faabiosr/cachego" ) type ( sqlite3 struct { db *sql.DB table string } ) // New creates an instance of Sqlite3 cache driver func New(db *sql.DB, table string) (cachego.Cache, error) { return &sqlite3{db, table}, createTable(db, table) } func createTable(db *sql.DB, table string) error { stmt := `CREATE TABLE IF NOT EXISTS %s ( key text PRIMARY KEY, value text NOT NULL, lifetime integer NOT NULL );` _, err := db.Exec(fmt.Sprintf(stmt, table)) return err } // Contains checks if cached key exists in Sqlite3 storage func (s *sqlite3) Contains(key string) bool { _, err := s.Fetch(key) return err == nil } // Delete the cached key from Sqlite3 storage func (s *sqlite3) Delete(key string) error { tx, err := s.db.Begin() if err != nil { return err } stmt, err := tx.Prepare(fmt.Sprintf(` DELETE FROM %s WHERE key = ? `, s.table)) if err != nil { return err } defer func() { _ = stmt.Close() }() if _, err := stmt.Exec(key); err != nil { _ = tx.Rollback() return err } return tx.Commit() } // Fetch retrieves the cached value from key of the Sqlite3 storage func (s *sqlite3) Fetch(key string) (string, error) { stmt, err := s.db.Prepare(fmt.Sprintf(` SELECT value, lifetime FROM %s WHERE key = ? `, s.table)) if err != nil { return "", err } defer func() { _ = stmt.Close() }() var value string var lifetime int64 if err := stmt.QueryRow(key).Scan(&value, &lifetime); err != nil { return "", err } if lifetime == 0 { return value, nil } if lifetime <= time.Now().Unix() { _ = s.Delete(key) return "", cachego.ErrCacheExpired } return value, nil } // FetchMulti retrieves multiple cached value from keys of the Sqlite3 storage func (s *sqlite3) FetchMulti(keys []string) map[string]string { result := make(map[string]string) for _, key := range keys { if value, err := s.Fetch(key); err == nil { result[key] = value } } return result } // Flush removes all cached keys of the Sqlite3 storage func (s *sqlite3) Flush() error { tx, err := s.db.Begin() if err != nil { return err } stmt, err := tx.Prepare(fmt.Sprintf("DELETE FROM %s", s.table)) if err != nil { return err } defer func() { _ = stmt.Close() }() if _, err := stmt.Exec(); err != nil { _ = tx.Rollback() return err } return tx.Commit() } // Save a value in Sqlite3 storage by key func (s *sqlite3) Save(key string, value string, lifeTime time.Duration) error { duration := int64(0) if lifeTime > 0 { duration = time.Now().Unix() + int64(lifeTime.Seconds()) } tx, err := s.db.Begin() if err != nil { return err } stmt, err := tx.Prepare(fmt.Sprintf(` INSERT OR REPLACE INTO %s (key, value, lifetime) VALUES (?, ?, ?) `, s.table)) if err != nil { return err } defer func() { _ = stmt.Close() }() if _, err := stmt.Exec(key, value, duration); err != nil { _ = tx.Rollback() return err } return tx.Commit() } ================================================ FILE: sqlite3/sqlite3_test.go ================================================ package sqlite3 import ( "database/sql" "fmt" "os" "testing" "time" _ "github.com/mattn/go-sqlite3" ) const ( testKey = "foo" testValue = "bar" testTable = "cache" testDBPath = "/cache.db" ) func TestSqlite3(t *testing.T) { dir, err := os.MkdirTemp("", t.Name()) if err != nil { t.Fatal(err) } db, err := sql.Open("sqlite3", dir+testDBPath) if err != nil { t.Skip(err) } t.Cleanup(func() { _ = os.RemoveAll(dir) }) c, err := New(db, testTable) if err != nil { t.Skip(err) } if err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil { t.Errorf("save fail: expected nil, got %v", err) } if _, err := c.Fetch(testKey); err == nil { t.Errorf("fetch fail: expected an error, got %v", err) } _ = c.Save(testKey, testValue, 10*time.Second) if res, _ := c.Fetch(testKey); res != testValue { t.Errorf("fetch fail, wrong value: expected %s, got %s", testValue, res) } _ = c.Save(testKey, testValue, 0) if !c.Contains(testKey) { t.Errorf("contains failed: the key %s should be exist", testKey) } _ = c.Save("bar", testValue, 0) if values := c.FetchMulti([]string{testKey, "bar"}); len(values) == 0 { t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values)) } if err := c.Flush(); err != nil { t.Errorf("flush failed: expected nil, got %v", err) } if c.Contains(testKey) { t.Errorf("contains failed: the key %s should not be exist", testKey) } } func TestSqlite3Fail(t *testing.T) { dir, err := os.MkdirTemp("", t.Name()) if err != nil { t.Fatal(err) } db, _ := sql.Open("sqlite3", dir+testDBPath) _ = db.Close() t.Cleanup(func() { _ = os.RemoveAll(dir) }) if _, err := New(db, testTable); err == nil { t.Errorf("constructor failed: expected an error, got %v", err) } db, _ = sql.Open("sqlite3", testDBPath) c, _ := New(db, testTable) _ = db.Close() if err := c.Save(testKey, testValue, 0); err == nil { t.Errorf("save failed: expected an error, got %v", err) } if err := c.Delete(testKey); err == nil { t.Errorf("delete failed: expected an error, got %v", err) } if err := c.Flush(); err == nil { t.Errorf("flush failed: expected an error, got %v", err) } db, _ = sql.Open("sqlite3", testDBPath) c, _ = New(db, testTable) _, _ = db.Exec(fmt.Sprintf("DROP TABLE %s;", testTable)) if err := c.Save(testKey, testValue, 0); err == nil { t.Errorf("save failed: expected an error, got %v", err) } if err := c.Delete(testKey); err == nil { t.Errorf("delete failed: expected an error, got %v", err) } if err := c.Flush(); err == nil { t.Errorf("flush failed: expected an error, got %v", err) } } ================================================ FILE: sync/README.md ================================================ # Cachego - Sync driver The drivers uses [golang sync](https://golang.org/pkg/sync/#Map) to store the cache data. ## Usage ```go package main import ( "log" "time" "github.com/faabiosr/cachego/sync" ) func main() { cache := sync.New() if err := cache.Save("user_id", "1", 10*time.Second); err != nil { log.Fatal(err) } id, err := cache.Fetch("user_id") if err != nil { log.Fatal(err) } log.Printf("user id: %s \n", id) } ``` ================================================ FILE: sync/map.go ================================================ // Package sync providers a cache driver that uses standard golang sync.Map. package sync import ( "errors" "sync" "time" "github.com/faabiosr/cachego" ) type ( syncMapItem struct { data string duration int64 } syncMap struct { storage *sync.Map } ) // New creates an instance of SyncMap cache driver func New() cachego.Cache { return &syncMap{&sync.Map{}} } func (sm *syncMap) read(key string) (*syncMapItem, error) { v, ok := sm.storage.Load(key) if !ok { return nil, errors.New("key not found") } item := v.(*syncMapItem) if item.duration == 0 { return item, nil } if item.duration <= time.Now().Unix() { _ = sm.Delete(key) return nil, cachego.ErrCacheExpired } return item, nil } // Contains checks if cached key exists in SyncMap storage func (sm *syncMap) Contains(key string) bool { _, err := sm.Fetch(key) return err == nil } // Delete the cached key from SyncMap storage func (sm *syncMap) Delete(key string) error { sm.storage.Delete(key) return nil } // Fetch retrieves the cached value from key of the SyncMap storage func (sm *syncMap) Fetch(key string) (string, error) { item, err := sm.read(key) if err != nil { return "", err } return item.data, nil } // FetchMulti retrieves multiple cached value from keys of the SyncMap storage func (sm *syncMap) FetchMulti(keys []string) map[string]string { result := make(map[string]string) for _, key := range keys { if value, err := sm.Fetch(key); err == nil { result[key] = value } } return result } // Flush removes all cached keys of the SyncMap storage func (sm *syncMap) Flush() error { sm.storage = &sync.Map{} return nil } // Save a value in SyncMap storage by key func (sm *syncMap) Save(key string, value string, lifeTime time.Duration) error { duration := int64(0) if lifeTime > 0 { duration = time.Now().Unix() + int64(lifeTime.Seconds()) } sm.storage.Store(key, &syncMapItem{value, duration}) return nil } ================================================ FILE: sync/map_test.go ================================================ package sync import ( "testing" "time" ) const ( testKey = "foo" testValue = "bar" ) func TestSyncMap(t *testing.T) { c := New() if err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil { t.Errorf("save fail: expected nil, got %v", err) } if _, err := c.Fetch(testKey); err == nil { t.Errorf("fetch fail: expected an error, got %v", err) } _ = c.Save(testKey, testValue, 10*time.Second) if res, _ := c.Fetch(testKey); res != testValue { t.Errorf("fetch fail, wrong value: expected %s, got %s", testValue, res) } _ = c.Save(testKey, testValue, 0) if !c.Contains(testKey) { t.Errorf("contains failed: the key %s should be exist", testKey) } _ = c.Save("bar", testValue, 0) if values := c.FetchMulti([]string{testKey, "bar"}); len(values) == 0 { t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values)) } if err := c.Flush(); err != nil { t.Errorf("flush failed: expected nil, got %v", err) } if c.Contains(testKey) { t.Errorf("contains failed: the key %s should not be exist", testKey) } }