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