Skip to content

Commit

Permalink
Merge pull request #2 from PragmaticEngineering/feature/kvstore
Browse files Browse the repository at this point in the history
updating repo to make some performance improvements
  • Loading branch information
James Rhoat authored Mar 13, 2024
2 parents da071da + ee5ebc2 commit 01a43bd
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 53 deletions.
4 changes: 2 additions & 2 deletions examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ func main() {
// The contextkey is a string that is used to store and retrieve values from context
// it should be unique to your application
contextKey := "my-unique-context-key"
m := pectx.NewManager(contextKey)
m := pectx.NewManager(contextKey, &pectx.Store{})

ctx := context.Background()

// setting data utilizes the KVStore interface
// This package provides a default implementation of KVStore called Store
f := pectx.NewStore(map[string]string{"my-key": "my-value"})
f := map[string]string{"my-key": "my-value"}

// set the value in context
// The set method returns a new context with the value set
Expand Down
9 changes: 4 additions & 5 deletions examples/duplicates/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,17 @@ func main() {
// The contextkey is a string that is used to store and retrieve values from context
// it should be unique to your application
contextKey := "my-unique-context-key"
m := pectx.NewManager(contextKey)
m := pectx.NewManager(contextKey, &pectx.Store{})

ctx := context.Background()

// setting data utilizes the KVStore interface
// This package provides a default implementation of KVStore called Field
f := pectx.NewStore(map[string]string{"my-key": "my-value", "my-key2": "my-value"})
// new key-value pairs you want to add to the context
f := map[string]string{"my-key": "my-value", "my-key2": "my-value"}
// set the value in context
// The set method returns a new context with the value set
// The Set function can accept multiple values to set in context.
ctx = m.Set(ctx, f)
f2 := pectx.NewStore(map[string]string{"my-key": "my-value2", "my-key2": "my-value2"})
f2 := map[string]string{"my-key": "my-value2", "my-key2": "my-value2"}
ctx = m.Set(ctx, f2)
// The value of my-key will be "my-value2"
// The last value set will be the value of the key
Expand Down
27 changes: 17 additions & 10 deletions manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ type Manager struct {
// The context key used to store the data in the context.
// This is to avoid collisions with other packages.
ctxKey ctxKey
// The store used to store the data.
store KVStore
}

// NewManager creates a new manager with the given context key.
func NewManager(uniqueKey string) *Manager {
return &Manager{ctxKey: ctxKey(uniqueKey)}
func NewManager(uniqueKey string, store KVStore) *Manager {
return &Manager{
ctxKey: ctxKey(uniqueKey),
store: store,
}
}

// Get retrieves the values from the context.
Expand All @@ -30,14 +35,13 @@ func (m *Manager) Get(ctx context.Context) (KVStore, bool) {

// Set stores the values in the context.
// If the key already exists, the value is overwritten.
func (m *Manager) Set(ctx context.Context, fields KVStore) context.Context {
func (m *Manager) Set(ctx context.Context, keyValues map[string]string) context.Context {
kvStores, exist := m.Get(ctx) // ensure the context is initialized
if !exist {
kvStores = &Store{}
kvStores = m.store
}
//
for _, key := range fields.ListKeys() {
value := fields.Get(key)

for key, value := range keyValues {
kvStores.Set(key, value)
}

Expand All @@ -47,13 +51,16 @@ func (m *Manager) Set(ctx context.Context, fields KVStore) context.Context {
// GetKeysAndValues retrieves the keys and values from the context.
// if the context is empty, it returns an empty slice.
func (m *Manager) GetKeysAndValues(ctx context.Context) []string {
keysAndValues := []string{}

fields, ok := m.Get(ctx)
if !ok {
return keysAndValues
return []string{}
}

for _, key := range fields.ListKeys() {
keys := fields.ListKeys()
keysAndValues := make([]string, 0, len(keys)*2)

for _, key := range keys {
value := fields.Get(key)
keysAndValues = append(keysAndValues, key, value)
}
Expand Down
130 changes: 101 additions & 29 deletions manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ const contextKey = "test123123123"

func TestManager_SetDuplicateKeys(t *testing.T) {
t.Parallel()
m := pectx.NewManager(contextKey)
m := pectx.NewManager(contextKey, &pectx.Store{})
ctx := context.Background()
f := pectx.NewStore(map[string]string{"k": "v"})
f := map[string]string{"k": "v"}
ctx = m.Set(ctx, f)
f2 := pectx.NewStore(map[string]string{"k": "v2"})
f2 := map[string]string{"k": "v2"}
ctx = m.Set(ctx, f2)
store, _ := m.Get(ctx)

Expand All @@ -33,29 +33,29 @@ func TestManager_Set(t *testing.T) {
testCases := []struct {
desc string
expected []string
input pectx.KVStore
input map[string]string
}{
{
desc: "Test that the context is set correctly",
expected: []string{"k", "v"},
input: &pectx.Store{"k": "v"},
input: map[string]string{"k": "v"},
},
{
desc: "Test if multiple fields are set correctly",
expected: []string{"k", "v", "k2", "v2"},
input: &pectx.Store{"k": "v", "k2": "v2"},
input: map[string]string{"k": "v", "k2": "v2"},
},
{
desc: "Test if the context is set correctly with no fields",
expected: []string{},
input: &pectx.Store{},
input: map[string]string{},
},
}
for _, tC := range testCases {
testCase := tC
t.Run(testCase.desc, func(t *testing.T) {
t.Parallel()
m := pectx.NewManager(contextKey)
m := pectx.NewManager(contextKey, &pectx.Store{})
ctx := context.Background()
ctx = m.Set(ctx, testCase.input)
keysAndValues := m.GetKeysAndValues(ctx)
Expand All @@ -69,59 +69,95 @@ func TestManager_Set(t *testing.T) {
}
}

func TestGetKeysAndValuesEmpty(t *testing.T) {
t.Parallel()
m := pectx.NewManager(contextKey, &pectx.Store{})
ctx := context.Background()
keysAndValues := m.GetKeysAndValues(ctx)
if len(keysAndValues) != 0 {
t.Errorf("expected 0, got %d", len(keysAndValues))
}
}

func TestGetKeysAndValues(t *testing.T) {
t.Run("Test if the context is empty", func(t *testing.T) {
t.Parallel()
m := pectx.NewManager(contextKey)
ctx := context.Background()
keysAndValues := m.GetKeysAndValues(ctx)

if len(keysAndValues) != 0 {
t.Errorf("expected empty slice, got %v", keysAndValues)
}
tc := []struct {
desc string
expected []string
input map[string]string
}{
{
desc: "Test if the context is set correctly",
expected: []string{"k", "v"},
input: map[string]string{"k": "v"},
},
{
desc: "Test if multiple fields are set correctly",
expected: []string{"k", "v", "k2", "v2"},
input: map[string]string{"k": "v", "k2": "v2"},
},
{
desc: "Test if the context is set correctly with no fields",
expected: []string{},
input: map[string]string{},
},
}

if !reflect.DeepEqual([]string{}, keysAndValues) {
t.Errorf("expected empty slice, got %v", keysAndValues)
}
})
for _, tC := range tc {
testCase := tC
t.Run(testCase.desc, func(t *testing.T) {
t.Parallel()
m := pectx.NewManager(contextKey, &pectx.Store{})
ctx := context.Background()
ctx = m.Set(ctx, testCase.input)
keysAndValues := m.GetKeysAndValues(ctx)
// sort the slices to compare them, as the order of the keys and values is not guaranteed.
sort.Strings(keysAndValues) // Sort the keysAndValues slice
sort.Strings(testCase.expected) // Sort the expected slice
if !reflect.DeepEqual(testCase.expected, keysAndValues) {
t.Errorf("expected %v, got %v", testCase.expected, keysAndValues)
}
})
}

}

func helperFields(amount int) pectx.KVStore {
func helperFields(amount int) map[string]string {

store := pectx.Store{}
store := map[string]string{}
for i := 0; i < amount; i++ {
store.Set(fmt.Sprintf("k%d", i), fmt.Sprintf("v%d", i))
key := fmt.Sprintf("k%d", i)
store[key] = fmt.Sprintf("v%d", i)

}
return &store
return store
}

func BenchmarkManager_Set(b *testing.B) {
var contextKey = "test123123123"

testCases := []struct {
name string
fields pectx.KVStore
fields map[string]string
mgr *pectx.Manager
ctx context.Context
}{
{
name: "manager with 1 field",
fields: helperFields(1),
mgr: pectx.NewManager(contextKey),
mgr: pectx.NewManager(contextKey, &pectx.Store{}),
ctx: context.Background(),
},
{
name: "manager with 10 fields",
fields: helperFields(10),
mgr: pectx.NewManager(contextKey),
mgr: pectx.NewManager(contextKey, &pectx.Store{}),
ctx: context.Background(),
},
{
name: "manager with 100 fields",
fields: helperFields(100),
mgr: pectx.NewManager(contextKey),
mgr: pectx.NewManager(contextKey, &pectx.Store{}),
ctx: context.Background(),
},
}
Expand All @@ -137,7 +173,7 @@ func BenchmarkManager_Set(b *testing.B) {

func helperFieldsGet(amount int) (context.Context, *pectx.Manager) {
ctx := context.Background()
m := pectx.NewManager(contextKey)
m := pectx.NewManager(contextKey, &pectx.Store{})

m.Set(ctx, helperFields(amount))

Expand Down Expand Up @@ -203,3 +239,39 @@ func BenchmarkManager_GetKeysAndValues(b *testing.B) {
})
}
}

func BenchmarkKeysAndValues_forloop(b *testing.B) {
manager := pectx.NewManager("uniqueKey", &pectx.Store{})

// Define test cases
testCases := []struct {
name string
data map[string]string
}{
{
name: "Set 1 key-value pair",
data: map[string]string{
"key1": "value1",
},
},
{
name: "Set 10 key-value pairs",
data: helperFields(10),
},
{
name: "Set 100 key-value pairs",
data: helperFields(100),
},
}

for _, tc := range testCases {
testcase := tc
b.Run(testcase.name, func(b *testing.B) {
ctx := manager.Set(context.Background(), testcase.data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.GetKeysAndValues(ctx)
}
})
}
}
18 changes: 11 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ pectx is a context helper for golang. It is a simple and easy to use package tha
// The contextkey is a string that is used to store and retrieve values from context
// it should be unique to your application
contextKey := "my-unique-context-key"
m := pectx.NewManager(contextKey)
m := pectx.NewManager(contextKey, &pectx.Store{})

ctx := context.Background()

// setting data utilizes the KVStore interface
// This package provides a default implementation of KVStore called Store
f := pectx.NewStore(map[string]string{"my-key":"my-value"})
f := map[string]string{"my-key": "my-value"}

// set the value in context
// The set method returns a new context with the value set
// The Set function can accept multiple values to set in context.
ctx := m.Set(ctx, f)
kvs := m.GetKeysAndValues(ctx)
fmt.Println(kvs) // [my-key my-value]
```

## Duplicate keys
Expand All @@ -35,22 +37,24 @@ Duplicate keys will be overwritten by the last value set in context.
// The contextkey is a string that is used to store and retrieve values from context
// it should be unique to your application
contextKey := "my-unique-context-key"
m := pectx.NewManager(contextKey)
m := pectx.NewManager(contextKey, &pectx.Store{})

ctx := context.Background()

// setting data utilizes the KVStore interface
// This package provides a default implementation of KVStore called Field
f := pectx.NewStore(map[string]string{"my-key":"my-value","my-key": "my-value2"})
// new key-value pairs you want to add to the context
f := map[string]string{"my-key": "my-value", "my-key2": "my-value"}
// set the value in context
// The set method returns a new context with the value set
// The Set function can accept multiple values to set in context.
ctx := m.Set(ctx, f, f2)
ctx = m.Set(ctx, f)
f2 := map[string]string{"my-key": "my-value2", "my-key2": "my-value2"}
ctx = m.Set(ctx, f2)

// The value of my-key will be "my-value2"
// The last value set will be the value of the key
// The order returned by GetKeysAndValues is not guaranteed as it uses a map to ensure unique keys
keysAndValues := m.GetKeysAndValues(ctx)
fmt.Println(keysAndValues) // [my-key my-value2 my-key2 my-value2]

```

Expand Down
5 changes: 5 additions & 0 deletions store.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package pectx

// This line of code doesn't do anything at runtime, but if Store does not implement KVStore,
// the code will not compile.
// This is a common idiom in Go for checking interface implementation at compile time.
var _ KVStore = (*Store)(nil)

// Store is an interface that defines the methods to store and retrieve data from a Store.
type KVStore interface {
KVGetter
Expand Down

0 comments on commit 01a43bd

Please sign in to comment.