Skip to content

Commit 51924bd

Browse files
authored
Add memkv module (#12)
* Add memkv module * Add docstrings
1 parent 85574ef commit 51924bd

File tree

11 files changed

+991
-0
lines changed

11 files changed

+991
-0
lines changed

Diff for: .github/workflows/memkv.yml

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
name: check-memkv
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
paths:
8+
- "memkv/**"
9+
- ".github/workflows/memkv.yml"
10+
pull_request:
11+
branches:
12+
- "*"
13+
paths:
14+
- "memkv/**"
15+
- ".github/workflows/memkv.yml"
16+
17+
jobs:
18+
19+
clean:
20+
name: clean
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 2
23+
strategy:
24+
matrix:
25+
go: [stable]
26+
fail-fast: true
27+
steps:
28+
- name: Checkout repo
29+
uses: actions/checkout@v4
30+
- name: Set up Go
31+
uses: actions/setup-go@v5
32+
with:
33+
go-version: ${{ matrix.go }}
34+
cache: true
35+
- name: Run go mod tidy
36+
working-directory: memkv
37+
run: go mod tidy && git diff --exit-code
38+
- name: Run go mod verify
39+
working-directory: memkv
40+
run: go mod verify
41+
- name: Run formatting
42+
working-directory: memkv
43+
run: go run golang.org/x/tools/cmd/goimports@latest -w . && git diff --exit-code
44+
45+
lint:
46+
name: lint
47+
runs-on: ubuntu-latest
48+
timeout-minutes: 4
49+
strategy:
50+
matrix:
51+
go: [stable]
52+
fail-fast: true
53+
steps:
54+
- name: Checkout repo
55+
uses: actions/checkout@v4
56+
- name: Set up Go
57+
uses: actions/setup-go@v5
58+
with:
59+
go-version: ${{ matrix.go }}
60+
cache: true
61+
- name: Run go linting
62+
uses: golangci/golangci-lint-action@v4
63+
with:
64+
version: latest
65+
args: --timeout=4m
66+
working-directory: memkv
67+
68+
test:
69+
name: test
70+
runs-on: ubuntu-latest
71+
timeout-minutes: 2
72+
strategy:
73+
matrix:
74+
go: [stable]
75+
fail-fast: true
76+
steps:
77+
- name: Checkout repo
78+
uses: actions/checkout@v4
79+
- name: Set up Go
80+
uses: actions/setup-go@v5
81+
with:
82+
go-version: ${{ matrix.go }}
83+
cache: true
84+
- name: Run tests
85+
working-directory: memkv
86+
run: go test -shuffle=on -v -count=1 -race -failfast -timeout=30s -covermode=atomic -coverprofile=coverage.out ./...
87+
- name: Codecov Coverage
88+
uses: codecov/codecov-action@v4
89+
with:
90+
token: ${{ secrets.CODECOV_TOKEN }}
91+
working-directory: memkv
92+
93+
benchmark:
94+
name: benchmark
95+
runs-on: ubuntu-latest
96+
timeout-minutes: 2
97+
strategy:
98+
matrix:
99+
go: [stable]
100+
fail-fast: true
101+
steps:
102+
- name: Checkout repo
103+
uses: actions/checkout@v4
104+
- name: Set up Go
105+
uses: actions/setup-go@v5
106+
with:
107+
go-version: ${{ matrix.go }}
108+
cache: true
109+
- name: Run benchmarks
110+
working-directory: memkv
111+
run: go test -v -shuffle=on -run=- -bench=. -benchtime=1x -timeout=10s ./...
112+
113+
build:
114+
name: build
115+
runs-on: ubuntu-latest
116+
timeout-minutes: 4
117+
strategy:
118+
matrix:
119+
go: [stable]
120+
fail-fast: true
121+
steps:
122+
- name: Checkout repo
123+
uses: actions/checkout@v4
124+
- name: Set up Go
125+
uses: actions/setup-go@v5
126+
with:
127+
go-version: ${{ matrix.go }}
128+
cache: true
129+
- name: Run go generate
130+
working-directory: memkv
131+
run: go generate ./... && git diff --exit-code
132+
- name: Run go build
133+
working-directory: memkv
134+
run: go build -o /dev/null ./...

Diff for: memkv/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Ben Wakeford
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.

Diff for: memkv/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# memkv
2+
[![go reference](https://pkg.go.dev/badge/github.com/wafer-bw/go-toolbox/memkv.svg)](https://pkg.go.dev/github.com/wafer-bw/go-toolbox/memkv)
3+
[![Go Report Card](https://goreportcard.com/badge/github.com/wafer-bw/go-toolbox/memkv)](https://goreportcard.com/report/github.com/wafer-bw/go-toolbox/memkv)

Diff for: memkv/go.mod

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/wafer-bw/go-toolbox/memkv
2+
3+
go 1.22.4
4+
5+
require github.com/stretchr/testify v1.9.0
6+
7+
require (
8+
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/pmezard/go-difflib v1.0.0 // indirect
10+
gopkg.in/yaml.v3 v3.0.1 // indirect
11+
)

Diff for: memkv/go.sum

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
6+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Diff for: memkv/internal/underlying/underlying.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Package underlying provides the underlying data structures for the key-value
2+
// store.
3+
package underlying
4+
5+
// Item is a wrapper around the instances of data to be stored allowing for
6+
// extensions in the future.
7+
type Item[K comparable, V any] struct {
8+
Value V
9+
}
10+
11+
// Data is a wrapper around any data types used to store data in the store
12+
// allowing for extensions in the future.
13+
type Data[K comparable, V any] struct {
14+
Items map[K]Item[K, V]
15+
}

Diff for: memkv/memkv.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package memkv
2+
3+
import (
4+
"sync"
5+
6+
"github.com/wafer-bw/go-toolbox/memkv/internal/underlying"
7+
)
8+
9+
// Store is a generic in-memory key-value store.
10+
type Store[K comparable, V any] struct {
11+
mu *sync.RWMutex
12+
capacity int
13+
data *underlying.Data[K, V]
14+
}
15+
16+
// New creates a new instance of [Store] with the provided capacity.
17+
//
18+
// - A capacity of zero means the store has no capacity limit.
19+
// - If the capacity is less than 0, it will be set to 0.
20+
func New[K comparable, V any](capacity int) *Store[K, V] {
21+
if capacity < 0 {
22+
capacity = 0
23+
}
24+
25+
return &Store[K, V]{
26+
mu: &sync.RWMutex{},
27+
capacity: capacity,
28+
data: &underlying.Data[K, V]{
29+
Items: make(map[K]underlying.Item[K, V], capacity),
30+
},
31+
}
32+
}
33+
34+
// Set the provided key-value pair in the store.
35+
func (s Store[K, V]) Set(key K, val V) error {
36+
s.mu.Lock()
37+
defer s.mu.Unlock()
38+
39+
if _, ok := s.data.Items[key]; !ok && s.capacity > 0 && len(s.data.Items) >= s.capacity {
40+
return &AtCapacityError{}
41+
}
42+
43+
s.data.Items[key] = underlying.Item[K, V]{Value: val}
44+
45+
return nil
46+
}
47+
48+
// Get the value associated with the provided key from the store if it exists.
49+
func (s Store[K, V]) Get(key K) (V, bool) {
50+
s.mu.RLock()
51+
defer s.mu.RUnlock()
52+
53+
item, ok := s.data.Items[key]
54+
55+
return item.Value, ok
56+
}
57+
58+
// Delete provided keys from the store.
59+
func (s Store[K, V]) Delete(keys ...K) {
60+
s.mu.Lock()
61+
defer s.mu.Unlock()
62+
63+
for _, key := range keys {
64+
delete(s.data.Items, key)
65+
}
66+
}
67+
68+
// Flush the cache, deleting all keys.
69+
func (s Store[K, V]) Flush() {
70+
s.mu.Lock()
71+
defer s.mu.Unlock()
72+
73+
clear(s.data.Items)
74+
}
75+
76+
// Len returns the number of items currently in the store.
77+
func (s Store[K, V]) Len() int {
78+
s.mu.RLock()
79+
defer s.mu.RUnlock()
80+
81+
return len(s.data.Items)
82+
}
83+
84+
// Items returns a map of all items currently in the store.
85+
func (s Store[K, V]) Items() map[K]V {
86+
s.mu.RLock()
87+
defer s.mu.RUnlock()
88+
89+
items := make(map[K]V, len(s.data.Items))
90+
for key, item := range s.data.Items {
91+
items[key] = item.Value
92+
}
93+
94+
return items
95+
}
96+
97+
// Keys returns a slice of all keys currently in the store.
98+
func (s Store[K, V]) Keys() []K {
99+
s.mu.RLock()
100+
defer s.mu.RUnlock()
101+
102+
keys := make([]K, 0, len(s.data.Items))
103+
for key := range s.data.Items {
104+
keys = append(keys, key)
105+
}
106+
107+
return keys
108+
}
109+
110+
// Values returns a slice of all values currently in the store.
111+
func (s Store[K, V]) Values() []V {
112+
s.mu.RLock()
113+
defer s.mu.RUnlock()
114+
115+
values := make([]V, 0, len(s.data.Items))
116+
for _, item := range s.data.Items {
117+
values = append(values, item.Value)
118+
}
119+
120+
return values
121+
}
122+
123+
// AtCapcityError occurs when the [Store] is at capacity and new items cannot be
124+
// added.
125+
type AtCapacityError struct{}
126+
127+
func (e *AtCapacityError) Error() string {
128+
return "store is at capacity"
129+
}

0 commit comments

Comments
 (0)