187 lines
3.2 KiB
Go
187 lines
3.2 KiB
Go
package etcdb
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
)
|
|
|
|
func Prefix(names ...string) string {
|
|
return path.Join(names...) + "/"
|
|
}
|
|
|
|
type Spec interface {
|
|
In(ctx context.Context) DB
|
|
}
|
|
|
|
type DB interface {
|
|
Get(key string) (value []byte, ok bool, err error)
|
|
Put(key string, value []byte) error
|
|
Del(key string) error
|
|
ForEach(callback func(key string, value []byte) (cont bool)) error
|
|
Watch(rev int64) <-chan WatchEvent
|
|
}
|
|
|
|
type WatchEvent struct {
|
|
Err error
|
|
Key string
|
|
Value []byte
|
|
Delete bool
|
|
NewRev int64
|
|
}
|
|
|
|
type TypedDB[T any] struct {
|
|
DB DB
|
|
Codec Codec
|
|
}
|
|
|
|
type TypedWatchEvent[T any] struct {
|
|
Err error
|
|
Key string
|
|
Value T
|
|
Delete bool
|
|
NewRev int64
|
|
}
|
|
|
|
func (e *TypedWatchEvent[T]) FromEvent(evt WatchEvent) {
|
|
e.Err = evt.Err
|
|
e.Key = evt.Key
|
|
e.Delete = evt.Delete
|
|
e.NewRev = evt.NewRev
|
|
}
|
|
|
|
func (tdb TypedDB[T]) Get(key string) (v T, ok bool, err error) {
|
|
raw, ok, err := tdb.DB.Get(key)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
err = tdb.Codec.Decode(raw, &v)
|
|
if err != nil {
|
|
err = fmt.Errorf("invalid value: %w", err)
|
|
ok = false
|
|
return
|
|
}
|
|
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
func (tdb TypedDB[T]) Put(key string, value T) (err error) {
|
|
ba, err := tdb.Codec.Encode(value)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to encode value: %w", err)
|
|
return
|
|
}
|
|
|
|
return tdb.DB.Put(key, ba)
|
|
}
|
|
|
|
func (tdb TypedDB[T]) Del(key string) error {
|
|
return tdb.DB.Del(key)
|
|
}
|
|
|
|
func (tdb TypedDB[T]) ForEach(callback func(key string, value T) (cont bool)) (err error) {
|
|
forEachErr := tdb.DB.ForEach(func(key string, encodedValue []byte) (cont bool) {
|
|
value := new(T)
|
|
if err := tdb.Codec.Decode(encodedValue, value); err != nil {
|
|
err = fmt.Errorf("invalid value at %q: %w", key, err)
|
|
return false
|
|
}
|
|
|
|
return callback(key, *value)
|
|
})
|
|
|
|
if err == nil && forEachErr != nil {
|
|
err = forEachErr
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (tdb TypedDB[T]) Watch(rev int64) <-chan TypedWatchEvent[T] {
|
|
ch := make(chan TypedWatchEvent[T], 1)
|
|
|
|
go func() {
|
|
defer close(ch)
|
|
|
|
inCh := tdb.DB.Watch(rev)
|
|
|
|
for evt := range inCh {
|
|
tevt := TypedWatchEvent[T]{}
|
|
tevt.FromEvent(evt)
|
|
|
|
if len(evt.Value) == 0 {
|
|
ch <- tevt
|
|
continue
|
|
}
|
|
|
|
value := new(T)
|
|
if err := tdb.Codec.Decode(evt.Value, value); err != nil {
|
|
tevt.Err = fmt.Errorf("invalid value: %w", err)
|
|
ch <- tevt
|
|
continue
|
|
}
|
|
|
|
tevt.Value = *value
|
|
ch <- tevt
|
|
}
|
|
}()
|
|
|
|
return ch
|
|
}
|
|
|
|
func (tdb TypedDB[T]) Sync(allData map[string]T) (err error) {
|
|
encodedData := map[string][]byte{}
|
|
|
|
for k, v := range allData {
|
|
encodedData[k], err = tdb.Codec.Encode(v)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to encode data[%q]: %w", k, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
seen := make(map[string]bool, len(allData))
|
|
|
|
forEachErr := tdb.DB.ForEach(func(key string, v []byte) (cont bool) {
|
|
seen[key] = true
|
|
newV, ok := encodedData[key]
|
|
|
|
if ok {
|
|
if !bytes.Equal(newV, v) {
|
|
err = tdb.DB.Put(key, newV)
|
|
}
|
|
} else {
|
|
err = tdb.Del(key)
|
|
}
|
|
|
|
return err == nil
|
|
})
|
|
|
|
if err == nil && forEachErr != nil {
|
|
err = forEachErr
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// add any missing data
|
|
for k, v := range encodedData {
|
|
if seen[k] {
|
|
continue
|
|
}
|
|
err = tdb.DB.Put(k, v)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|