Moved storage to a separate package
This commit is contained in:
parent
2aa395ed9c
commit
620308fab5
@ -1,8 +1,6 @@
|
|||||||
package ocean_test
|
package ocean_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/go/assert"
|
"git.akyoto.dev/go/assert"
|
||||||
@ -10,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func BenchmarkGet(b *testing.B) {
|
func BenchmarkGet(b *testing.B) {
|
||||||
users, err := ocean.New[User]("test")
|
users, err := ocean.New[User]("test", nil)
|
||||||
assert.Nil(b, err)
|
assert.Nil(b, err)
|
||||||
|
|
||||||
defer users.Sync()
|
defer users.Sync()
|
||||||
@ -23,7 +21,7 @@ func BenchmarkGet(b *testing.B) {
|
|||||||
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
users.Get("1")
|
_, _ = users.Get("1")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -31,7 +29,7 @@ func BenchmarkGet(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkSet(b *testing.B) {
|
func BenchmarkSet(b *testing.B) {
|
||||||
users, err := ocean.New[User]("test")
|
users, err := ocean.New[User]("test", nil)
|
||||||
assert.Nil(b, err)
|
assert.Nil(b, err)
|
||||||
|
|
||||||
defer users.Sync()
|
defer users.Sync()
|
||||||
@ -52,7 +50,7 @@ func BenchmarkSet(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkDelete(b *testing.B) {
|
func BenchmarkDelete(b *testing.B) {
|
||||||
users, err := ocean.New[User]("test")
|
users, err := ocean.New[User]("test", nil)
|
||||||
assert.Nil(b, err)
|
assert.Nil(b, err)
|
||||||
|
|
||||||
defer users.Sync()
|
defer users.Sync()
|
||||||
@ -70,27 +68,10 @@ func BenchmarkDelete(b *testing.B) {
|
|||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkColdStart(b *testing.B) {
|
func BenchmarkNew(b *testing.B) {
|
||||||
users, err := ocean.New[User]("test")
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
assert.Nil(b, err)
|
for pb.Next() {
|
||||||
|
ocean.New[User]("test", nil)
|
||||||
defer users.Sync()
|
|
||||||
defer users.Clear()
|
|
||||||
|
|
||||||
b.Run("100", func(b *testing.B) {
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
users.Set(strconv.Itoa(i), &User{Name: fmt.Sprintf("User %d", i)})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
again, err := ocean.New[User]("test")
|
|
||||||
assert.Nil(b, err)
|
|
||||||
assert.NotNil(b, again)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.StopTimer()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,13 @@ import (
|
|||||||
type Collection[T any] interface {
|
type Collection[T any] interface {
|
||||||
All() <-chan *T
|
All() <-chan *T
|
||||||
Clear()
|
Clear()
|
||||||
|
Data() *sync.Map
|
||||||
Delete(key string)
|
Delete(key string)
|
||||||
Exists(key string) bool
|
Exists(key string) bool
|
||||||
Filter(func(*T) bool) <-chan *T
|
Filter(func(*T) bool) <-chan *T
|
||||||
Get(key string) (value *T, err error)
|
Get(key string) (value *T, err error)
|
||||||
|
Name() string
|
||||||
|
Root() string
|
||||||
Set(key string, value *T)
|
Set(key string, value *T)
|
||||||
Sync()
|
Sync()
|
||||||
}
|
}
|
||||||
@ -27,29 +30,31 @@ type collection[T any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new collection with the given name.
|
// New creates a new collection with the given name.
|
||||||
func New[T any](directories ...string) (*collection[T], error) {
|
func New[T any](namespace string, storage Storage[T]) (*collection[T], error) {
|
||||||
name := reflect.TypeOf((*T)(nil)).Elem().Name()
|
name := reflect.TypeOf((*T)(nil)).Elem().Name()
|
||||||
|
c := &collection[T]{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if storage == nil {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
directories = append([]string{home, ".ocean"}, directories...)
|
c.storage = storage
|
||||||
directory := filepath.Join(directories...)
|
c.root = filepath.Join(home, ".ocean", namespace)
|
||||||
err = os.MkdirAll(directory, 0700)
|
err = os.MkdirAll(c.root, 0700)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &collection[T]{
|
return c, storage.Init(c)
|
||||||
name: name,
|
|
||||||
root: directory,
|
|
||||||
storage: &FileStorage[T]{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, c.storage.Init(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All returns a channel of all objects in the collection.
|
// All returns a channel of all objects in the collection.
|
||||||
@ -76,9 +81,19 @@ func (c *collection[T]) Clear() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Data returns the internal data structure.
|
||||||
|
func (c *collection[T]) Data() *sync.Map {
|
||||||
|
return &c.data
|
||||||
|
}
|
||||||
|
|
||||||
// Delete deletes a key from the collection.
|
// Delete deletes a key from the collection.
|
||||||
func (c *collection[T]) Delete(key string) {
|
func (c *collection[T]) Delete(key string) {
|
||||||
c.data.Delete(key)
|
c.data.Delete(key)
|
||||||
|
|
||||||
|
if c.storage == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.storage.Delete(key)
|
c.storage.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,9 +133,24 @@ func (c *collection[T]) Filter(filter func(*T) bool) <-chan *T {
|
|||||||
return channel
|
return channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the collection.
|
||||||
|
func (c *collection[T]) Name() string {
|
||||||
|
return c.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root returns the root path.
|
||||||
|
func (c *collection[T]) Root() string {
|
||||||
|
return c.root
|
||||||
|
}
|
||||||
|
|
||||||
// Set sets the value for the given key.
|
// Set sets the value for the given key.
|
||||||
func (c *collection[T]) Set(key string, value *T) {
|
func (c *collection[T]) Set(key string, value *T) {
|
||||||
c.data.Store(key, value)
|
c.data.Store(key, value)
|
||||||
|
|
||||||
|
if c.storage == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err := c.storage.Set(key, value)
|
err := c.storage.Set(key, value)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -130,5 +160,9 @@ func (c *collection[T]) Set(key string, value *T) {
|
|||||||
|
|
||||||
// Sync waits for all disk writes to finish before it returns.
|
// Sync waits for all disk writes to finish before it returns.
|
||||||
func (c *collection[T]) Sync() {
|
func (c *collection[T]) Sync() {
|
||||||
|
if c.storage == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.storage.Sync()
|
c.storage.Sync()
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCollection(t *testing.T) {
|
func TestCollection(t *testing.T) {
|
||||||
users, err := ocean.New[User]("test")
|
users, err := ocean.New[User]("test", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
defer users.Sync()
|
defer users.Sync()
|
||||||
@ -84,28 +84,6 @@ func TestCollection(t *testing.T) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Persistence", func(t *testing.T) {
|
|
||||||
users.Sync()
|
|
||||||
|
|
||||||
again, err := ocean.New[User]("test")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
user, err := again.Get("1")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, user)
|
|
||||||
assert.Equal(t, user.Name, "User 1")
|
|
||||||
|
|
||||||
user, err = again.Get("2")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, user)
|
|
||||||
assert.Equal(t, user.Name, "User 2")
|
|
||||||
|
|
||||||
user, err = again.Get("3")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, user)
|
|
||||||
assert.Equal(t, user.Name, "User 3")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Delete", func(t *testing.T) {
|
t.Run("Delete", func(t *testing.T) {
|
||||||
assert.True(t, users.Exists("1"))
|
assert.True(t, users.Exists("1"))
|
||||||
assert.True(t, users.Exists("2"))
|
assert.True(t, users.Exists("2"))
|
||||||
|
@ -15,7 +15,7 @@ go get git.akyoto.dev/go/ocean
|
|||||||
type User struct { Name string }
|
type User struct { Name string }
|
||||||
|
|
||||||
// Create a new collection
|
// Create a new collection
|
||||||
users := ocean.New[User]("todolist")
|
users := ocean.New[User]("todolist", &storage.File[User]{})
|
||||||
|
|
||||||
// Store some data
|
// Store some data
|
||||||
users.Set("1", &User{Name: "User 1"})
|
users.Set("1", &User{Name: "User 1"})
|
||||||
@ -33,7 +33,7 @@ for user := range users.All() {
|
|||||||
|
|
||||||
Data will be stored in `~/.ocean/todolist/User.dat`.
|
Data will be stored in `~/.ocean/todolist/User.dat`.
|
||||||
|
|
||||||
## Example file: User.dat
|
## Format
|
||||||
|
|
||||||
```json
|
```json
|
||||||
1
|
1
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package ocean
|
package ocean
|
||||||
|
|
||||||
type Storage[T any] interface {
|
type Storage[T any] interface {
|
||||||
|
Init(c Collection[T]) error
|
||||||
Delete(key string) error
|
Delete(key string) error
|
||||||
Init(c *collection[T]) error
|
|
||||||
Set(key string, value *T) error
|
Set(key string, value *T) error
|
||||||
Sync()
|
Sync()
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package ocean
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
@ -1,37 +1,42 @@
|
|||||||
package ocean
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.akyoto.dev/go/ocean"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DirectoryStorage creates a directory and stores every record in a separate file.
|
// Directory creates a directory and stores every record in a separate file.
|
||||||
type DirectoryStorage[T any] struct {
|
type Directory[T any] struct {
|
||||||
collection *collection[T]
|
collection ocean.Collection[T]
|
||||||
directory string
|
directory string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init loads all existing records from the directory.
|
// Init loads all existing records from the directory.
|
||||||
func (ds *DirectoryStorage[T]) Init(c *collection[T]) error {
|
func (ds *Directory[T]) Init(c ocean.Collection[T]) error {
|
||||||
ds.collection = c
|
ds.collection = c
|
||||||
ds.directory = filepath.Join(c.root, c.name)
|
ds.directory = filepath.Join(c.Root(), c.Name())
|
||||||
os.Mkdir(ds.directory, 0700)
|
os.Mkdir(ds.directory, 0700)
|
||||||
return ds.read()
|
return ds.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set saves the value in a file.
|
// Set saves the value in a file.
|
||||||
func (ds *DirectoryStorage[T]) Set(key string, value *T) error {
|
func (ds *Directory[T]) Set(key string, value *T) error {
|
||||||
return ds.writeFile(key, value)
|
return ds.writeFile(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the file for the given key.
|
// Delete deletes the file for the given key.
|
||||||
func (ds *DirectoryStorage[T]) Delete(key string) error {
|
func (ds *Directory[T]) Delete(key string) error {
|
||||||
return os.Remove(ds.keyFile(key))
|
return os.Remove(ds.keyFile(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync does nothing when using directory storage.
|
||||||
|
func (ds *Directory[T]) Sync() {}
|
||||||
|
|
||||||
// read loads the collection data from the disk.
|
// read loads the collection data from the disk.
|
||||||
func (ds *DirectoryStorage[T]) read() error {
|
func (ds *Directory[T]) read() error {
|
||||||
dir, err := os.Open(ds.directory)
|
dir, err := os.Open(ds.directory)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -53,7 +58,7 @@ func (ds *DirectoryStorage[T]) read() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readFile loads a single file from the disk.
|
// readFile loads a single file from the disk.
|
||||||
func (ds *DirectoryStorage[T]) readFile(fileName string) error {
|
func (ds *Directory[T]) readFile(fileName string) error {
|
||||||
fileName = filepath.Join(ds.directory, fileName)
|
fileName = filepath.Join(ds.directory, fileName)
|
||||||
file, err := os.Open(fileName)
|
file, err := os.Open(fileName)
|
||||||
|
|
||||||
@ -71,12 +76,12 @@ func (ds *DirectoryStorage[T]) readFile(fileName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
key := strings.TrimSuffix(fileName, ".json")
|
key := strings.TrimSuffix(fileName, ".json")
|
||||||
ds.collection.data.Store(key, value)
|
ds.collection.Data().Store(key, value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeFile writes the value for the key to disk as a JSON file.
|
// writeFile writes the value for the key to disk as a JSON file.
|
||||||
func (ds *DirectoryStorage[T]) writeFile(key string, value *T) error {
|
func (ds *Directory[T]) writeFile(key string, value *T) error {
|
||||||
fileName := ds.keyFile(key)
|
fileName := ds.keyFile(key)
|
||||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||||
|
|
||||||
@ -96,6 +101,6 @@ func (ds *DirectoryStorage[T]) writeFile(key string, value *T) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// keyFile returns the file path for the given key.
|
// keyFile returns the file path for the given key.
|
||||||
func (ds *DirectoryStorage[T]) keyFile(key string) string {
|
func (ds *Directory[T]) keyFile(key string) string {
|
||||||
return filepath.Join(ds.directory, key+".json")
|
return filepath.Join(ds.directory, key+".json")
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ocean
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
@ -1,4 +1,4 @@
|
|||||||
package ocean
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -10,23 +10,25 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.akyoto.dev/go/ocean"
|
||||||
)
|
)
|
||||||
|
|
||||||
const diskWriteInterval = 100 * time.Millisecond
|
const diskWriteInterval = 100 * time.Millisecond
|
||||||
|
|
||||||
type FileStorage[T any] struct {
|
type File[T any] struct {
|
||||||
collection *collection[T]
|
collection ocean.Collection[T]
|
||||||
dirty atomic.Uint32
|
dirty atomic.Uint32
|
||||||
sync chan struct{}
|
sync chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FileStorage[T]) Init(c *collection[T]) error {
|
func (fs *File[T]) Init(c ocean.Collection[T]) error {
|
||||||
fs.collection = c
|
fs.collection = c
|
||||||
fs.sync = make(chan struct{})
|
fs.sync = make(chan struct{})
|
||||||
|
|
||||||
go fs.flushWorker()
|
go fs.flushWorker()
|
||||||
|
|
||||||
fileName := filepath.Join(c.root, c.name+".dat")
|
fileName := filepath.Join(c.Root(), c.Name()+".dat")
|
||||||
file, err := os.Open(fileName)
|
file, err := os.Open(fileName)
|
||||||
|
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@ -41,21 +43,21 @@ func (fs *FileStorage[T]) Init(c *collection[T]) error {
|
|||||||
return fs.readFrom(file)
|
return fs.readFrom(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FileStorage[T]) Delete(key string) error {
|
func (fs *File[T]) Delete(key string) error {
|
||||||
fs.dirty.Store(1)
|
fs.dirty.Store(1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FileStorage[T]) Set(key string, value *T) error {
|
func (fs *File[T]) Set(key string, value *T) error {
|
||||||
fs.dirty.Store(1)
|
fs.dirty.Store(1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FileStorage[T]) Sync() {
|
func (fs *File[T]) Sync() {
|
||||||
<-fs.sync
|
<-fs.sync
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FileStorage[T]) flushWorker() {
|
func (fs *File[T]) flushWorker() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(diskWriteInterval)
|
time.Sleep(diskWriteInterval)
|
||||||
|
|
||||||
@ -76,8 +78,8 @@ func (fs *FileStorage[T]) flushWorker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FileStorage[T]) flush() error {
|
func (fs *File[T]) flush() error {
|
||||||
oldPath := filepath.Join(fs.collection.root, fs.collection.name+".dat")
|
oldPath := filepath.Join(fs.collection.Root(), fs.collection.Name()+".dat")
|
||||||
newPath := oldPath + ".tmp"
|
newPath := oldPath + ".tmp"
|
||||||
file, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
file, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||||
|
|
||||||
@ -117,7 +119,7 @@ func (fs *FileStorage[T]) flush() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readFrom reads the entire collection.
|
// readFrom reads the entire collection.
|
||||||
func (fs *FileStorage[T]) readFrom(stream io.Reader) error {
|
func (fs *File[T]) readFrom(stream io.Reader) error {
|
||||||
var (
|
var (
|
||||||
key string
|
key string
|
||||||
value []byte
|
value []byte
|
||||||
@ -139,7 +141,7 @@ func (fs *FileStorage[T]) readFrom(stream io.Reader) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.collection.data.Store(key, object)
|
fs.collection.Data().Store(key, object)
|
||||||
key = ""
|
key = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +149,7 @@ func (fs *FileStorage[T]) readFrom(stream io.Reader) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// writeTo writes the entire collection.
|
// writeTo writes the entire collection.
|
||||||
func (fs *FileStorage[T]) writeTo(writer io.Writer) error {
|
func (fs *File[T]) writeTo(writer io.Writer) error {
|
||||||
stringWriter, ok := writer.(io.StringWriter)
|
stringWriter, ok := writer.(io.StringWriter)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -156,7 +158,7 @@ func (fs *FileStorage[T]) writeTo(writer io.Writer) error {
|
|||||||
|
|
||||||
records := []keyValue{}
|
records := []keyValue{}
|
||||||
|
|
||||||
fs.collection.data.Range(func(key, value any) bool {
|
fs.collection.Data().Range(func(key, value any) bool {
|
||||||
records = append(records, keyValue{
|
records = append(records, keyValue{
|
||||||
key: key.(string),
|
key: key.(string),
|
||||||
value: value,
|
value: value,
|
@ -1,4 +1,4 @@
|
|||||||
package ocean
|
package storage
|
||||||
|
|
||||||
type keyValue struct {
|
type keyValue struct {
|
||||||
key string
|
key string
|
49
storage/storage_test.go
Normal file
49
storage/storage_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package storage_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.akyoto.dev/go/assert"
|
||||||
|
"git.akyoto.dev/go/ocean"
|
||||||
|
"git.akyoto.dev/go/ocean/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterface(t *testing.T) {
|
||||||
|
var _ ocean.Storage[string] = (*storage.File[string])(nil)
|
||||||
|
var _ ocean.Storage[string] = (*storage.Directory[string])(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPersistence(t *testing.T) {
|
||||||
|
users, err := ocean.New[User]("test", &storage.File[User]{})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
defer users.Sync()
|
||||||
|
defer users.Clear()
|
||||||
|
|
||||||
|
users.Set("1", &User{Name: "User 1"})
|
||||||
|
users.Set("2", &User{Name: "User 2"})
|
||||||
|
users.Set("3", &User{Name: "User 3"})
|
||||||
|
users.Sync()
|
||||||
|
|
||||||
|
reload, err := ocean.New[User]("test", &storage.File[User]{})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
user, err := reload.Get("1")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, user)
|
||||||
|
assert.Equal(t, user.Name, "User 1")
|
||||||
|
|
||||||
|
user, err = reload.Get("2")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, user)
|
||||||
|
assert.Equal(t, user.Name, "User 2")
|
||||||
|
|
||||||
|
user, err = reload.Get("3")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, user)
|
||||||
|
assert.Equal(t, user.Name, "User 3")
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user