package sus
import(
`os`
`io/ioutil`
)
// Creates and configures a store that stores entities by converting them to and from json []byte data and keeps them in the local file system.
func NewJsonFileStore(storeDir string, idf IdFactory, vf VersionFactory) (Store, error) {
return NewFileStore(storeDir, `json`, jsonMarshaler, jsonUnmarshaler, idf, vf)
}
// Creates and configures a store that stores entities by converting them to and from []byte and keeps them in the local file system.
func NewFileStore(storeDir string, fileExt string, m Marshaler, un Unmarshaler, idf IdFactory, vf VersionFactory) (Store, error) {
err := os.MkdirAll(storeDir, os.ModeDir)
if err != nil {
return nil, err
}
getFileName := func(id string) string {
return storeDir + `/` + id + `.` + fileExt
}
get := func(id string) ([]byte, error) {
fn := getFileName(id)
if _, err := os.Stat(fn); err != nil {
if os.IsNotExist(err) {
err = localEntityDoesNotExistError{id}
}
return nil, err
}
return ioutil.ReadFile(fn)
}
put := func(id string, d []byte) error {
return ioutil.WriteFile(getFileName(id), d, os.ModeAppend)
}
del := func(id string) error {
return os.Remove(getFileName(id))
}
isNonExtantError := func(err error) bool {
_, ok := err.(localEntityDoesNotExistError)
return ok
}
return NewMutexByteStore(get, put, del, m, un, idf, vf, isNonExtantError), nil
}
package sus
import(
`encoding/json`
)
func jsonMarshaler(v Version)([]byte, error){
return json.Marshal(v)
}
func jsonUnmarshaler(d []byte, v Version) error{
return json.Unmarshal(d, v)
}
// Creates and configures a store that stores entities by converting them to and from json []byte data and keeps them in the local system memory.
func NewJsonMemoryStore(idf IdFactory, vf VersionFactory) Store {
return NewMemoryStore(jsonMarshaler, jsonUnmarshaler, idf, vf)
}
// Creates and configures a store that stores entities by converting them to and from []byte and keeps them in the local system memory.
func NewMemoryStore(m Marshaler, un Unmarshaler, idf IdFactory, vf VersionFactory) Store {
store := map[string][]byte{}
get := func(id string) ([]byte, error) {
var err error
d, exists := store[id]
if !exists {
err = localEntityDoesNotExistError{id}
}
return d, err
}
put := func(id string, d []byte) error {
store[id] = d
return nil
}
del := func(id string) error {
delete(store, id)
return nil
}
isNonExtantError := func(err error) bool {
_, ok := err.(localEntityDoesNotExistError)
return ok
}
return NewMutexByteStore(get, put, del, m, un, idf, vf, isNonExtantError)
}
package sus
import(
`sync`
)
type Marshaler func(src Version) ([]byte, error)
type Unmarshaler func(data []byte, dst Version) error
type ByteGetter func(id string) ([]byte, error)
type BytePutter func(id string, d []byte) error
type Deleter func(id string) error
// Creates and configures a store that stores entities by converting them to and from []byte and ensures versioning correctness with mutex locks.
func NewMutexByteStore(bg ByteGetter, bp BytePutter, d Deleter, m Marshaler, un Unmarshaler, idf IdFactory, vf VersionFactory, inee IsNonExtantError) Store {
mtx := sync.Mutex{}
getMulti := func(ids []string) ([]Version, error) {
var err error
var d []byte
count := len(ids)
vs := make([]Version, count, count)
for i := 0; i < count; i++{
d, err = bg(ids[i])
if err != nil {
break
}
vs[i] = vf()
err = un(d, vs[i])
if err != nil {
break
}
}
if err != nil {
vs = nil
}
return vs, err
}
putMulti := func(ids []string, vs []Version) error {
var err error
var d []byte
count := len(ids)
for i := 0; i < count; i++{
d, err = m(vs[i])
if err != nil {
break
}
err = bp(ids[i], d)
}
return err
}
delMulti := func(ids []string) (err error) {
count := len(ids)
for i := 0; i < count; i++ {
err = d(ids[i])
if err != nil {
break
}
}
return
}
rit := func(tran Transaction) error {
mtx.Lock()
defer mtx.Unlock()
return tran()
}
return NewStore(getMulti, putMulti, delMulti, idf, vf, inee, rit)
}
type localEntityDoesNotExistError struct{
id string
}
func (e localEntityDoesNotExistError) Error() string{
return `entity with id "`+e.id+`" does not exist`
}
/*
Package sus provides data storage for entities that require sequential updates.
Any type of datastore can be created in the same manner as those available by default
in sus, Memory/File/AppEngine.
*/
package sus
import(
`fmt`
)
// The interface that struct entities must include as anonymous fields in order to be used with sus stores.
type Version interface{
GetVersion() int
IncrementVersion()
DecrementVersion()
}
// The core sus interface.
type Store interface{
Create() (id string, v Version, err error)
CreateMulti(count uint) (ids []string, vs []Version, err error)
Read(id string) (v Version, err error)
ReadMulti(ids []string) (vs []Version, err error)
Update(id string, v Version) error
UpdateMulti(ids []string, vs []Version) error
Delete(id string) error
DeleteMulti(ids []string) error
}
type IdFactory func() string
type VersionFactory func() Version
type RunInTransaction func(tran Transaction) error
type Transaction func() error
type GetMulti func(ids []string) ([]Version, error)
type PutMulti func(ids []string, vs []Version) error
type DeleteMulti func(ids []string) error
type IsNonExtantError func(error) bool
// Create and configure a core store.
func NewStore(gm GetMulti, pm PutMulti, dm DeleteMulti, idf IdFactory, vf VersionFactory, inee IsNonExtantError, rit RunInTransaction) Store {
return &store{gm, pm, dm, idf, vf, inee, rit}
}
type store struct{
getMulti GetMulti
putMulti PutMulti
deleteMulti DeleteMulti
idFactory IdFactory
versionFactory VersionFactory
isNonExtantError IsNonExtantError
runInTransaction RunInTransaction
}
// Creates a new versioned entity.
func (s *store) Create() (id string, v Version, err error) {
ids, vs, err := s.CreateMulti(1)
if len(ids) == 1 && len(vs) == 1 {
id = ids[0]
v = vs[0]
}
return
}
// Creates a set of new versioned entities.
func (s *store) CreateMulti(count uint) (ids []string, vs []Version, err error) {
if count == 0 {
return
}
icount := int(count)
err = s.runInTransaction(func() error {
ids = make([]string, count, count)
vs = make([]Version, count, count)
for i := 0; i < icount; i++ {
ids[i] = s.idFactory()
vs[i] = s.versionFactory()
}
return s.putMulti(ids, vs)
})
return
}
// Fetches the versioned entity with id.
func (s *store) Read(id string) (v Version, err error) {
vs, err := s.ReadMulti([]string{id})
if len(vs) == 1 {
v = vs[0]
}
return
}
// Fetches the versioned entities with id's.
func (s *store) ReadMulti(ids []string) (vs []Version, err error) {
if len(ids) == 0 {
return
}
err = s.runInTransaction(func() error {
vs, err = s.getMulti(ids)
if err != nil {
if s.isNonExtantError(err) {
err = &nonExtantError{err}
}
}
return err
})
return
}
// Updates the versioned entity with id.
func (s *store) Update(id string, v Version) (err error) {
err = s.UpdateMulti([]string{id}, []Version{v})
return
}
// Updates the versioned entities with id's.
func (s *store) UpdateMulti(ids []string, vs []Version) (err error) {
count := len(ids)
if count != len(vs) {
err = &idCountNotEqualToEntityCountError{count, len(vs)}
return
}
if count == 0 {
return
}
err = s.runInTransaction(func() error {
oldVs, err := s.getMulti(ids)
if err != nil {
if s.isNonExtantError(err) {
err = &nonExtantError{err}
}
} else {
reverseI := 0
for i := 0; i < count; i++ {
if oldVs[i].GetVersion() != vs[i].GetVersion() {
err = &nonsequentialUpdateError{ids[i]}
reverseI = i
break;
}
vs[i].IncrementVersion()
}
if err != nil {
for i := 0; i < reverseI; i++ {
vs[i].DecrementVersion()
}
} else {
err = s.putMulti(ids, vs)
}
}
return err
})
return
}
// Deletes the versioned entity with id.
func (s *store) Delete(id string) error {
return s.DeleteMulti([]string{id})
}
// Deletes the versioned entities with id's.
func (s *store) DeleteMulti(ids []string) error {
if len(ids) == 0 {
return nil
}
return s.runInTransaction(func() error {
return s.deleteMulti(ids)
})
}
type nonExtantError struct{
inner error
}
func (e *nonExtantError) Error() string { return `Non extant error, inner error message: ` + e.inner.Error()}
type nonsequentialUpdateError struct{
id string
}
func (e *nonsequentialUpdateError) Error() string { return `nonsequential update for entity with id "`+e.id+`"` }
type idCountNotEqualToEntityCountError struct{
idCount int
eCount int
}
func (e *idCountNotEqualToEntityCountError) Error() string { return fmt.Sprintf(`id count (%d) not equal to entity count (%d)`, e.idCount, e.eCount) }