refactor(store): rename key-value store surface
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
45bd96387a
commit
e922734c6e
6 changed files with 125 additions and 124 deletions
76
docs/RFC.md
76
docs/RFC.md
|
|
@ -1097,8 +1097,8 @@ Returned when a key does not exist.
|
|||
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_, err := s.Get("config", "missing")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_, err := keyValueStore.Get("config", "missing")
|
||||
if core.Is(err, store.NotFoundError) {
|
||||
// handle missing key
|
||||
}
|
||||
|
|
@ -1114,94 +1114,94 @@ options := store.Options{Path: ":memory:"}
|
|||
_ = options
|
||||
```
|
||||
|
||||
### New(options Options) (*Store, error)
|
||||
### New(options Options) (*KeyValueStore, error)
|
||||
|
||||
Creates a new `Store` backed by the configured SQLite path.
|
||||
Creates a new `KeyValueStore` backed by the configured SQLite path.
|
||||
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Set("config", "theme", "midnight")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Set("config", "theme", "midnight")
|
||||
```
|
||||
|
||||
### Store
|
||||
### KeyValueStore
|
||||
|
||||
Group-namespaced key-value store.
|
||||
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Set("config", "theme", "midnight")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Set("config", "theme", "midnight")
|
||||
```
|
||||
|
||||
**Close() error**
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Close()
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Close()
|
||||
```
|
||||
|
||||
**Get(group, key string) (string, error)**
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Set("config", "theme", "midnight")
|
||||
value, _ := s.Get("config", "theme")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Set("config", "theme", "midnight")
|
||||
value, _ := keyValueStore.Get("config", "theme")
|
||||
```
|
||||
|
||||
**Set(group, key, value string) error**
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Set("config", "theme", "midnight")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Set("config", "theme", "midnight")
|
||||
```
|
||||
|
||||
**Delete(group, key string) error**
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Set("config", "theme", "midnight")
|
||||
_ = s.Delete("config", "theme")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Set("config", "theme", "midnight")
|
||||
_ = keyValueStore.Delete("config", "theme")
|
||||
```
|
||||
|
||||
**Count(group string) (int, error)**
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Set("config", "theme", "midnight")
|
||||
count, _ := s.Count("config")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Set("config", "theme", "midnight")
|
||||
count, _ := keyValueStore.Count("config")
|
||||
```
|
||||
|
||||
**DeleteGroup(group string) error**
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Set("config", "theme", "midnight")
|
||||
_ = s.DeleteGroup("config")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Set("config", "theme", "midnight")
|
||||
_ = keyValueStore.DeleteGroup("config")
|
||||
```
|
||||
|
||||
**GetAll(group string) (map[string]string, error)**
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Set("config", "theme", "midnight")
|
||||
all, _ := s.GetAll("config")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Set("config", "theme", "midnight")
|
||||
all, _ := keyValueStore.GetAll("config")
|
||||
```
|
||||
|
||||
**Render(tmplStr, group string) (string, error)**
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = s.Set("user", "name", "alice")
|
||||
out, _ := s.Render("hello {{ .name }}", "user")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
_ = keyValueStore.Set("user", "name", "alice")
|
||||
renderedText, _ := keyValueStore.Render("hello {{ .name }}", "user")
|
||||
```
|
||||
|
||||
**AsMedium() *Medium**
|
||||
Example:
|
||||
```go
|
||||
s, _ := store.New(store.Options{Path: ":memory:"})
|
||||
m := s.AsMedium()
|
||||
_ = m.Write("config/theme", "midnight")
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
medium := keyValueStore.AsMedium()
|
||||
_ = medium.Write("config/theme", "midnight")
|
||||
```
|
||||
|
||||
### NewMedium(options Options) (*Medium, error)
|
||||
|
|
@ -1216,7 +1216,7 @@ _ = m.Write("config/theme", "midnight")
|
|||
|
||||
### Medium
|
||||
|
||||
Adapter that maps `group/key` paths onto a `Store`.
|
||||
Adapter that maps `group/key` paths onto a `KeyValueStore`.
|
||||
|
||||
Example:
|
||||
```go
|
||||
|
|
@ -1224,12 +1224,12 @@ m, _ := store.NewMedium(store.Options{Path: ":memory:"})
|
|||
_ = m.Write("config/theme", "midnight")
|
||||
```
|
||||
|
||||
**Store() *Store**
|
||||
**KeyValueStore() *KeyValueStore**
|
||||
Example:
|
||||
```go
|
||||
m, _ := store.NewMedium(store.Options{Path: ":memory:"})
|
||||
s := m.Store()
|
||||
_ = s.Set("config", "theme", "midnight")
|
||||
keyValueStore := m.KeyValueStore()
|
||||
_ = keyValueStore.Set("config", "theme", "midnight")
|
||||
```
|
||||
|
||||
**Close() error**
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ A thread-safe `Medium` backed by Borg's `DataNode` (an in-memory `fs.FS` with ta
|
|||
|
||||
The store package provides two complementary APIs:
|
||||
|
||||
### Store (key-value)
|
||||
### KeyValueStore (key-value)
|
||||
|
||||
A group-namespaced key-value store backed by SQLite:
|
||||
|
||||
|
|
@ -135,22 +135,23 @@ Operations: `Get`, `Set`, `Delete`, `Count`, `DeleteGroup`, `GetAll`, `Render`.
|
|||
The `Render` method loads all key-value pairs from a group into a `map[string]string` and executes a Go `text/template` against them:
|
||||
|
||||
```go
|
||||
s.Set("user", "pool", "pool.lthn.io:3333")
|
||||
s.Set("user", "wallet", "iz...")
|
||||
out, _ := s.Render(`{"pool":"{{ .pool }}"}`, "user")
|
||||
// out: {"pool":"pool.lthn.io:3333"}
|
||||
keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
keyValueStore.Set("user", "pool", "pool.lthn.io:3333")
|
||||
keyValueStore.Set("user", "wallet", "iz...")
|
||||
renderedText, _ := keyValueStore.Render(`{"pool":"{{ .pool }}"}`, "user")
|
||||
// renderedText: {"pool":"pool.lthn.io:3333"}
|
||||
```
|
||||
|
||||
### store.Medium (Medium adapter)
|
||||
|
||||
Wraps a `Store` to satisfy the `Medium` interface. Paths are split as `group/key`:
|
||||
Wraps a `KeyValueStore` to satisfy the `Medium` interface. Paths are split as `group/key`:
|
||||
|
||||
- `Read("config/theme")` calls `Get("config", "theme")`
|
||||
- `List("")` returns all groups as directories
|
||||
- `List("config")` returns all keys in the `config` group as files
|
||||
- `IsDir("config")` returns true if the group has entries
|
||||
|
||||
You can create it directly (`NewMedium(":memory:")`) or adapt an existing store (`store.AsMedium()`).
|
||||
You can create it directly (`store.NewMedium(store.Options{Path: ":memory:"})`) or adapt an existing store (`keyValueStore.AsMedium()`).
|
||||
|
||||
|
||||
## sigil Package
|
||||
|
|
@ -270,7 +271,7 @@ Application code
|
|||
+-- sqlite.Medium --> modernc.org/sqlite
|
||||
+-- node.Node --> in-memory map + tar serialisation
|
||||
+-- datanode.Medium --> Borg DataNode + sync.RWMutex
|
||||
+-- store.Medium --> store.Store (SQLite KV) --> Medium adapter
|
||||
+-- store.Medium --> store.KeyValueStore (SQLite KV) --> Medium adapter
|
||||
+-- MemoryMedium --> map[string]string (for tests)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
// Example: entries, _ := medium.List("")
|
||||
// Example: entries, _ := medium.List("app")
|
||||
type Medium struct {
|
||||
store *Store
|
||||
keyValueStore *KeyValueStore
|
||||
}
|
||||
|
||||
var _ coreio.Medium = (*Medium)(nil)
|
||||
|
|
@ -23,26 +23,26 @@ var _ coreio.Medium = (*Medium)(nil)
|
|||
// Example: medium, _ := store.NewMedium(store.Options{Path: "config.db"})
|
||||
// Example: _ = medium.Write("app/theme", "midnight")
|
||||
func NewMedium(options Options) (*Medium, error) {
|
||||
store, err := New(options)
|
||||
keyValueStore, err := New(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Medium{store: store}, nil
|
||||
return &Medium{keyValueStore: keyValueStore}, nil
|
||||
}
|
||||
|
||||
// Example: medium := keyValueStore.AsMedium()
|
||||
func (store *Store) AsMedium() *Medium {
|
||||
return &Medium{store: store}
|
||||
func (keyValueStore *KeyValueStore) AsMedium() *Medium {
|
||||
return &Medium{keyValueStore: keyValueStore}
|
||||
}
|
||||
|
||||
// Example: keyValueStore := medium.Store()
|
||||
func (medium *Medium) Store() *Store {
|
||||
return medium.store
|
||||
// Example: keyValueStore := medium.KeyValueStore()
|
||||
func (medium *Medium) KeyValueStore() *KeyValueStore {
|
||||
return medium.keyValueStore
|
||||
}
|
||||
|
||||
// Example: _ = medium.Close()
|
||||
func (medium *Medium) Close() error {
|
||||
return medium.store.Close()
|
||||
return medium.keyValueStore.Close()
|
||||
}
|
||||
|
||||
func splitGroupKeyPath(entryPath string) (group, key string) {
|
||||
|
|
@ -63,7 +63,7 @@ func (medium *Medium) Read(entryPath string) (string, error) {
|
|||
if key == "" {
|
||||
return "", core.E("store.Read", "path must include group/key", fs.ErrInvalid)
|
||||
}
|
||||
return medium.store.Get(group, key)
|
||||
return medium.keyValueStore.Get(group, key)
|
||||
}
|
||||
|
||||
func (medium *Medium) Write(entryPath, content string) error {
|
||||
|
|
@ -71,7 +71,7 @@ func (medium *Medium) Write(entryPath, content string) error {
|
|||
if key == "" {
|
||||
return core.E("store.Write", "path must include group/key", fs.ErrInvalid)
|
||||
}
|
||||
return medium.store.Set(group, key, content)
|
||||
return medium.keyValueStore.Set(group, key, content)
|
||||
}
|
||||
|
||||
// Example: _ = medium.WriteMode("app/theme", "midnight", 0600)
|
||||
|
|
@ -89,7 +89,7 @@ func (medium *Medium) IsFile(entryPath string) bool {
|
|||
if key == "" {
|
||||
return false
|
||||
}
|
||||
_, err := medium.store.Get(group, key)
|
||||
_, err := medium.keyValueStore.Get(group, key)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ func (medium *Medium) Delete(entryPath string) error {
|
|||
return core.E("store.Delete", "path is required", fs.ErrInvalid)
|
||||
}
|
||||
if key == "" {
|
||||
entryCount, err := medium.store.Count(group)
|
||||
entryCount, err := medium.keyValueStore.Count(group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -108,7 +108,7 @@ func (medium *Medium) Delete(entryPath string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
return medium.store.Delete(group, key)
|
||||
return medium.keyValueStore.Delete(group, key)
|
||||
}
|
||||
|
||||
func (medium *Medium) DeleteAll(entryPath string) error {
|
||||
|
|
@ -117,9 +117,9 @@ func (medium *Medium) DeleteAll(entryPath string) error {
|
|||
return core.E("store.DeleteAll", "path is required", fs.ErrInvalid)
|
||||
}
|
||||
if key == "" {
|
||||
return medium.store.DeleteGroup(group)
|
||||
return medium.keyValueStore.DeleteGroup(group)
|
||||
}
|
||||
return medium.store.Delete(group, key)
|
||||
return medium.keyValueStore.Delete(group, key)
|
||||
}
|
||||
|
||||
func (medium *Medium) Rename(oldPath, newPath string) error {
|
||||
|
|
@ -128,14 +128,14 @@ func (medium *Medium) Rename(oldPath, newPath string) error {
|
|||
if oldKey == "" || newKey == "" {
|
||||
return core.E("store.Rename", "both paths must include group/key", fs.ErrInvalid)
|
||||
}
|
||||
value, err := medium.store.Get(oldGroup, oldKey)
|
||||
value, err := medium.keyValueStore.Get(oldGroup, oldKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := medium.store.Set(newGroup, newKey, value); err != nil {
|
||||
if err := medium.keyValueStore.Set(newGroup, newKey, value); err != nil {
|
||||
return err
|
||||
}
|
||||
return medium.store.Delete(oldGroup, oldKey)
|
||||
return medium.keyValueStore.Delete(oldGroup, oldKey)
|
||||
}
|
||||
|
||||
// Example: entries, _ := medium.List("app")
|
||||
|
|
@ -143,7 +143,7 @@ func (medium *Medium) List(entryPath string) ([]fs.DirEntry, error) {
|
|||
group, key := splitGroupKeyPath(entryPath)
|
||||
|
||||
if group == "" {
|
||||
rows, err := medium.store.database.Query("SELECT DISTINCT group_name FROM entries ORDER BY group_name")
|
||||
rows, err := medium.keyValueStore.database.Query("SELECT DISTINCT group_name FROM entries ORDER BY group_name")
|
||||
if err != nil {
|
||||
return nil, core.E("store.List", "query groups", err)
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@ func (medium *Medium) List(entryPath string) ([]fs.DirEntry, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
all, err := medium.store.GetAll(group)
|
||||
all, err := medium.keyValueStore.GetAll(group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ func (medium *Medium) Stat(entryPath string) (fs.FileInfo, error) {
|
|||
return nil, core.E("store.Stat", "path is required", fs.ErrInvalid)
|
||||
}
|
||||
if key == "" {
|
||||
entryCount, err := medium.store.Count(group)
|
||||
entryCount, err := medium.keyValueStore.Count(group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ func (medium *Medium) Stat(entryPath string) (fs.FileInfo, error) {
|
|||
}
|
||||
return &keyValueFileInfo{name: group, isDir: true}, nil
|
||||
}
|
||||
value, err := medium.store.Get(group, key)
|
||||
value, err := medium.keyValueStore.Get(group, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -206,7 +206,7 @@ func (medium *Medium) Open(entryPath string) (fs.File, error) {
|
|||
if key == "" {
|
||||
return nil, core.E("store.Open", "path must include group/key", fs.ErrInvalid)
|
||||
}
|
||||
value, err := medium.store.Get(group, key)
|
||||
value, err := medium.keyValueStore.Get(group, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -218,7 +218,7 @@ func (medium *Medium) Create(entryPath string) (goio.WriteCloser, error) {
|
|||
if key == "" {
|
||||
return nil, core.E("store.Create", "path must include group/key", fs.ErrInvalid)
|
||||
}
|
||||
return &keyValueWriteCloser{store: medium.store, group: group, key: key}, nil
|
||||
return &keyValueWriteCloser{keyValueStore: medium.keyValueStore, group: group, key: key}, nil
|
||||
}
|
||||
|
||||
func (medium *Medium) Append(entryPath string) (goio.WriteCloser, error) {
|
||||
|
|
@ -226,8 +226,8 @@ func (medium *Medium) Append(entryPath string) (goio.WriteCloser, error) {
|
|||
if key == "" {
|
||||
return nil, core.E("store.Append", "path must include group/key", fs.ErrInvalid)
|
||||
}
|
||||
existingValue, _ := medium.store.Get(group, key)
|
||||
return &keyValueWriteCloser{store: medium.store, group: group, key: key, data: []byte(existingValue)}, nil
|
||||
existingValue, _ := medium.keyValueStore.Get(group, key)
|
||||
return &keyValueWriteCloser{keyValueStore: medium.keyValueStore, group: group, key: key, data: []byte(existingValue)}, nil
|
||||
}
|
||||
|
||||
func (medium *Medium) ReadStream(entryPath string) (goio.ReadCloser, error) {
|
||||
|
|
@ -235,7 +235,7 @@ func (medium *Medium) ReadStream(entryPath string) (goio.ReadCloser, error) {
|
|||
if key == "" {
|
||||
return nil, core.E("store.ReadStream", "path must include group/key", fs.ErrInvalid)
|
||||
}
|
||||
value, err := medium.store.Get(group, key)
|
||||
value, err := medium.keyValueStore.Get(group, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -252,10 +252,10 @@ func (medium *Medium) Exists(entryPath string) bool {
|
|||
return false
|
||||
}
|
||||
if key == "" {
|
||||
entryCount, err := medium.store.Count(group)
|
||||
entryCount, err := medium.keyValueStore.Count(group)
|
||||
return err == nil && entryCount > 0
|
||||
}
|
||||
_, err := medium.store.Get(group, key)
|
||||
_, err := medium.keyValueStore.Get(group, key)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +264,7 @@ func (medium *Medium) IsDir(entryPath string) bool {
|
|||
if key != "" || group == "" {
|
||||
return false
|
||||
}
|
||||
entryCount, err := medium.store.Count(group)
|
||||
entryCount, err := medium.keyValueStore.Count(group)
|
||||
return err == nil && entryCount > 0
|
||||
}
|
||||
|
||||
|
|
@ -334,7 +334,7 @@ func (file *keyValueFile) Read(buffer []byte) (int, error) {
|
|||
func (file *keyValueFile) Close() error { return nil }
|
||||
|
||||
type keyValueWriteCloser struct {
|
||||
store *Store
|
||||
keyValueStore *KeyValueStore
|
||||
group string
|
||||
key string
|
||||
data []byte
|
||||
|
|
@ -346,5 +346,5 @@ func (writer *keyValueWriteCloser) Write(data []byte) (int, error) {
|
|||
}
|
||||
|
||||
func (writer *keyValueWriteCloser) Close() error {
|
||||
return writer.store.Set(writer.group, writer.key, string(writer.data))
|
||||
return writer.keyValueStore.Set(writer.group, writer.key, string(writer.data))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,12 +183,12 @@ func TestKeyValueMedium_Append_Good(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestKeyValueMedium_AsMedium_Good(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
|
||||
m := s.AsMedium()
|
||||
m := keyValueStore.AsMedium()
|
||||
require.NoError(t, m.Write("group/key", "val"))
|
||||
|
||||
value, err := s.Get("group", "key")
|
||||
value, err := keyValueStore.Get("group", "key")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "val", value)
|
||||
|
||||
|
|
@ -197,11 +197,11 @@ func TestKeyValueMedium_AsMedium_Good(t *testing.T) {
|
|||
assert.Equal(t, "val", value)
|
||||
}
|
||||
|
||||
func TestKeyValueMedium_Store_Good(t *testing.T) {
|
||||
func TestKeyValueMedium_KeyValueStore_Good(t *testing.T) {
|
||||
m := newTestKeyValueMedium(t)
|
||||
|
||||
assert.NotNil(t, m.Store())
|
||||
assert.Same(t, m.Store(), m.Store())
|
||||
assert.NotNil(t, m.KeyValueStore())
|
||||
assert.Same(t, m.KeyValueStore(), m.KeyValueStore())
|
||||
}
|
||||
|
||||
func TestKeyValueMedium_EnsureDir_ReadWrite_Good(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
var NotFoundError = errors.New("key not found")
|
||||
|
||||
// Example: keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
type Store struct {
|
||||
type KeyValueStore struct {
|
||||
database *sql.DB
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ type Options struct {
|
|||
|
||||
// Example: keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
// Example: _ = keyValueStore.Set("app", "theme", "midnight")
|
||||
func New(options Options) (*Store, error) {
|
||||
func New(options Options) (*KeyValueStore, error) {
|
||||
if options.Path == "" {
|
||||
return nil, core.E("store.New", "database path is required", fs.ErrInvalid)
|
||||
}
|
||||
|
|
@ -47,18 +47,18 @@ func New(options Options) (*Store, error) {
|
|||
database.Close()
|
||||
return nil, core.E("store.New", "create schema", err)
|
||||
}
|
||||
return &Store{database: database}, nil
|
||||
return &KeyValueStore{database: database}, nil
|
||||
}
|
||||
|
||||
// Example: _ = keyValueStore.Close()
|
||||
func (store *Store) Close() error {
|
||||
return store.database.Close()
|
||||
func (keyValueStore *KeyValueStore) Close() error {
|
||||
return keyValueStore.database.Close()
|
||||
}
|
||||
|
||||
// Example: theme, _ := keyValueStore.Get("app", "theme")
|
||||
func (store *Store) Get(group, key string) (string, error) {
|
||||
func (keyValueStore *KeyValueStore) Get(group, key string) (string, error) {
|
||||
var value string
|
||||
err := store.database.QueryRow("SELECT entry_value FROM entries WHERE group_name = ? AND entry_key = ?", group, key).Scan(&value)
|
||||
err := keyValueStore.database.QueryRow("SELECT entry_value FROM entries WHERE group_name = ? AND entry_key = ?", group, key).Scan(&value)
|
||||
if err == sql.ErrNoRows {
|
||||
return "", core.E("store.Get", core.Concat("not found: ", group, "/", key), NotFoundError)
|
||||
}
|
||||
|
|
@ -69,8 +69,8 @@ func (store *Store) Get(group, key string) (string, error) {
|
|||
}
|
||||
|
||||
// Example: _ = keyValueStore.Set("app", "theme", "midnight")
|
||||
func (store *Store) Set(group, key, value string) error {
|
||||
_, err := store.database.Exec(
|
||||
func (keyValueStore *KeyValueStore) Set(group, key, value string) error {
|
||||
_, err := keyValueStore.database.Exec(
|
||||
`INSERT INTO entries (group_name, entry_key, entry_value) VALUES (?, ?, ?)
|
||||
ON CONFLICT(group_name, entry_key) DO UPDATE SET entry_value = excluded.entry_value`,
|
||||
group, key, value,
|
||||
|
|
@ -82,8 +82,8 @@ func (store *Store) Set(group, key, value string) error {
|
|||
}
|
||||
|
||||
// Example: _ = keyValueStore.Delete("app", "theme")
|
||||
func (store *Store) Delete(group, key string) error {
|
||||
_, err := store.database.Exec("DELETE FROM entries WHERE group_name = ? AND entry_key = ?", group, key)
|
||||
func (keyValueStore *KeyValueStore) Delete(group, key string) error {
|
||||
_, err := keyValueStore.database.Exec("DELETE FROM entries WHERE group_name = ? AND entry_key = ?", group, key)
|
||||
if err != nil {
|
||||
return core.E("store.Delete", "exec", err)
|
||||
}
|
||||
|
|
@ -91,9 +91,9 @@ func (store *Store) Delete(group, key string) error {
|
|||
}
|
||||
|
||||
// Example: count, _ := keyValueStore.Count("app")
|
||||
func (store *Store) Count(group string) (int, error) {
|
||||
func (keyValueStore *KeyValueStore) Count(group string) (int, error) {
|
||||
var count int
|
||||
err := store.database.QueryRow("SELECT COUNT(*) FROM entries WHERE group_name = ?", group).Scan(&count)
|
||||
err := keyValueStore.database.QueryRow("SELECT COUNT(*) FROM entries WHERE group_name = ?", group).Scan(&count)
|
||||
if err != nil {
|
||||
return 0, core.E("store.Count", "query", err)
|
||||
}
|
||||
|
|
@ -101,8 +101,8 @@ func (store *Store) Count(group string) (int, error) {
|
|||
}
|
||||
|
||||
// Example: _ = keyValueStore.DeleteGroup("app")
|
||||
func (store *Store) DeleteGroup(group string) error {
|
||||
_, err := store.database.Exec("DELETE FROM entries WHERE group_name = ?", group)
|
||||
func (keyValueStore *KeyValueStore) DeleteGroup(group string) error {
|
||||
_, err := keyValueStore.database.Exec("DELETE FROM entries WHERE group_name = ?", group)
|
||||
if err != nil {
|
||||
return core.E("store.DeleteGroup", "exec", err)
|
||||
}
|
||||
|
|
@ -110,8 +110,8 @@ func (store *Store) DeleteGroup(group string) error {
|
|||
}
|
||||
|
||||
// Example: values, _ := keyValueStore.GetAll("app")
|
||||
func (store *Store) GetAll(group string) (map[string]string, error) {
|
||||
rows, err := store.database.Query("SELECT entry_key, entry_value FROM entries WHERE group_name = ?", group)
|
||||
func (keyValueStore *KeyValueStore) GetAll(group string) (map[string]string, error) {
|
||||
rows, err := keyValueStore.database.Query("SELECT entry_key, entry_value FROM entries WHERE group_name = ?", group)
|
||||
if err != nil {
|
||||
return nil, core.E("store.GetAll", "query", err)
|
||||
}
|
||||
|
|
@ -134,8 +134,8 @@ func (store *Store) GetAll(group string) (map[string]string, error) {
|
|||
// Example: keyValueStore, _ := store.New(store.Options{Path: ":memory:"})
|
||||
// Example: _ = keyValueStore.Set("user", "name", "alice")
|
||||
// Example: renderedText, _ := keyValueStore.Render("hello {{ .name }}", "user")
|
||||
func (store *Store) Render(templateText, group string) (string, error) {
|
||||
rows, err := store.database.Query("SELECT entry_key, entry_value FROM entries WHERE group_name = ?", group)
|
||||
func (keyValueStore *KeyValueStore) Render(templateText, group string) (string, error) {
|
||||
rows, err := keyValueStore.database.Query("SELECT entry_key, entry_value FROM entries WHERE group_name = ?", group)
|
||||
if err != nil {
|
||||
return "", core.E("store.Render", "query", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newTestStore(t *testing.T) *Store {
|
||||
func newTestKeyValueStore(t *testing.T) *KeyValueStore {
|
||||
t.Helper()
|
||||
|
||||
keyValueStore, err := New(Options{Path: ":memory:"})
|
||||
|
|
@ -18,18 +18,18 @@ func newTestStore(t *testing.T) *Store {
|
|||
return keyValueStore
|
||||
}
|
||||
|
||||
func TestStore_New_Options_Good(t *testing.T) {
|
||||
keyValueStore := newTestStore(t)
|
||||
func TestKeyValueStore_New_Options_Good(t *testing.T) {
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
assert.NotNil(t, keyValueStore)
|
||||
}
|
||||
|
||||
func TestStore_New_Options_Bad(t *testing.T) {
|
||||
func TestKeyValueStore_New_Options_Bad(t *testing.T) {
|
||||
_, err := New(Options{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestStore_SetGet_Good(t *testing.T) {
|
||||
keyValueStore := newTestStore(t)
|
||||
func TestKeyValueStore_SetGet_Good(t *testing.T) {
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
|
||||
err := keyValueStore.Set("config", "theme", "dark")
|
||||
require.NoError(t, err)
|
||||
|
|
@ -39,15 +39,15 @@ func TestStore_SetGet_Good(t *testing.T) {
|
|||
assert.Equal(t, "dark", value)
|
||||
}
|
||||
|
||||
func TestStore_Get_NotFound_Bad(t *testing.T) {
|
||||
keyValueStore := newTestStore(t)
|
||||
func TestKeyValueStore_Get_NotFound_Bad(t *testing.T) {
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
|
||||
_, err := keyValueStore.Get("config", "missing")
|
||||
assert.ErrorIs(t, err, NotFoundError)
|
||||
}
|
||||
|
||||
func TestStore_Delete_Good(t *testing.T) {
|
||||
keyValueStore := newTestStore(t)
|
||||
func TestKeyValueStore_Delete_Good(t *testing.T) {
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
|
||||
_ = keyValueStore.Set("config", "key", "val")
|
||||
err := keyValueStore.Delete("config", "key")
|
||||
|
|
@ -57,8 +57,8 @@ func TestStore_Delete_Good(t *testing.T) {
|
|||
assert.ErrorIs(t, err, NotFoundError)
|
||||
}
|
||||
|
||||
func TestStore_Count_Good(t *testing.T) {
|
||||
keyValueStore := newTestStore(t)
|
||||
func TestKeyValueStore_Count_Good(t *testing.T) {
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
|
||||
_ = keyValueStore.Set("group", "a", "1")
|
||||
_ = keyValueStore.Set("group", "b", "2")
|
||||
|
|
@ -69,8 +69,8 @@ func TestStore_Count_Good(t *testing.T) {
|
|||
assert.Equal(t, 2, count)
|
||||
}
|
||||
|
||||
func TestStore_DeleteGroup_Good(t *testing.T) {
|
||||
keyValueStore := newTestStore(t)
|
||||
func TestKeyValueStore_DeleteGroup_Good(t *testing.T) {
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
|
||||
_ = keyValueStore.Set("group", "a", "1")
|
||||
_ = keyValueStore.Set("group", "b", "2")
|
||||
|
|
@ -81,8 +81,8 @@ func TestStore_DeleteGroup_Good(t *testing.T) {
|
|||
assert.Equal(t, 0, count)
|
||||
}
|
||||
|
||||
func TestStore_GetAll_Good(t *testing.T) {
|
||||
keyValueStore := newTestStore(t)
|
||||
func TestKeyValueStore_GetAll_Good(t *testing.T) {
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
|
||||
_ = keyValueStore.Set("group", "a", "1")
|
||||
_ = keyValueStore.Set("group", "b", "2")
|
||||
|
|
@ -93,16 +93,16 @@ func TestStore_GetAll_Good(t *testing.T) {
|
|||
assert.Equal(t, map[string]string{"a": "1", "b": "2"}, all)
|
||||
}
|
||||
|
||||
func TestStore_GetAll_Empty_Good(t *testing.T) {
|
||||
keyValueStore := newTestStore(t)
|
||||
func TestKeyValueStore_GetAll_Empty_Good(t *testing.T) {
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
|
||||
all, err := keyValueStore.GetAll("empty")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, all)
|
||||
}
|
||||
|
||||
func TestStore_Render_Good(t *testing.T) {
|
||||
keyValueStore := newTestStore(t)
|
||||
func TestKeyValueStore_Render_Good(t *testing.T) {
|
||||
keyValueStore := newTestKeyValueStore(t)
|
||||
|
||||
_ = keyValueStore.Set("user", "pool", "pool.lthn.io:3333")
|
||||
_ = keyValueStore.Set("user", "wallet", "iz...")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue