444 lines
9.2 KiB
Go
444 lines
9.2 KiB
Go
// Note: this file is used as a proof of concept for having a generic
|
|
// keyvalue store backed by specific implementations of kv.Store.
|
|
package kv
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/influxdata/platform"
|
|
)
|
|
|
|
var (
|
|
exampleBucket = []byte("examplesv1")
|
|
exampleIndex = []byte("exampleindexv1")
|
|
)
|
|
|
|
// ExampleService is an example user like service built on a generic kv store.
|
|
type ExampleService struct {
|
|
kv Store
|
|
idGenerator platform.IDGenerator
|
|
}
|
|
|
|
// NewExampleService creates an instance of an example service.
|
|
func NewExampleService(kv Store, idGen platform.IDGenerator) *ExampleService {
|
|
return &ExampleService{
|
|
kv: kv,
|
|
idGenerator: idGen,
|
|
}
|
|
}
|
|
|
|
// Initialize creates the buckets for the example service
|
|
func (c *ExampleService) Initialize() error {
|
|
return c.kv.Update(func(tx Tx) error {
|
|
if _, err := tx.Bucket([]byte(exampleBucket)); err != nil {
|
|
return err
|
|
}
|
|
if _, err := tx.Bucket([]byte(exampleIndex)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// FindUserByID retrieves a example by id.
|
|
func (c *ExampleService) FindUserByID(ctx context.Context, id platform.ID) (*platform.User, error) {
|
|
var u *platform.User
|
|
|
|
err := c.kv.View(func(tx Tx) error {
|
|
usr, err := c.findUserByID(ctx, tx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u = usr
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, &platform.Error{
|
|
Op: "kv/" + platform.OpFindUserByID,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
return u, nil
|
|
}
|
|
|
|
func (c *ExampleService) findUserByID(ctx context.Context, tx Tx, id platform.ID) (*platform.User, error) {
|
|
encodedID, err := id.Encode()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b, err := tx.Bucket(exampleBucket)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v, err := b.Get(encodedID)
|
|
if err == ErrKeyNotFound {
|
|
return nil, &platform.Error{
|
|
Code: platform.ENotFound,
|
|
Msg: "user not found",
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var u platform.User
|
|
if err := json.Unmarshal(v, &u); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &u, nil
|
|
}
|
|
|
|
// FindUserByName returns a example by name for a particular example.
|
|
func (c *ExampleService) FindUserByName(ctx context.Context, n string) (*platform.User, error) {
|
|
var u *platform.User
|
|
|
|
err := c.kv.View(func(tx Tx) error {
|
|
usr, err := c.findUserByName(ctx, tx, n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u = usr
|
|
return nil
|
|
})
|
|
|
|
return u, err
|
|
}
|
|
|
|
func (c *ExampleService) findUserByName(ctx context.Context, tx Tx, n string) (*platform.User, error) {
|
|
b, err := tx.Bucket(exampleIndex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
uid, err := b.Get(exampleIndexKey(n))
|
|
if err == ErrKeyNotFound {
|
|
return nil, &platform.Error{
|
|
Code: platform.ENotFound,
|
|
Msg: "user not found",
|
|
Op: "kv/" + platform.OpFindUser,
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var id platform.ID
|
|
if err := id.Decode(uid); err != nil {
|
|
return nil, err
|
|
}
|
|
return c.findUserByID(ctx, tx, id)
|
|
}
|
|
|
|
// FindUser retrives a example using an arbitrary example filter.
|
|
// Filters using ID, or Name should be efficient.
|
|
// Other filters will do a linear scan across examples until it finds a match.
|
|
func (c *ExampleService) FindUser(ctx context.Context, filter platform.UserFilter) (*platform.User, error) {
|
|
if filter.ID != nil {
|
|
u, err := c.FindUserByID(ctx, *filter.ID)
|
|
if err != nil {
|
|
return nil, &platform.Error{
|
|
Op: "kv/" + platform.OpFindUser,
|
|
Err: err,
|
|
}
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
if filter.Name != nil {
|
|
return c.FindUserByName(ctx, *filter.Name)
|
|
}
|
|
|
|
filterFn := filterExamplesFn(filter)
|
|
|
|
var u *platform.User
|
|
err := c.kv.View(func(tx Tx) error {
|
|
return forEachExample(ctx, tx, func(usr *platform.User) bool {
|
|
if filterFn(usr) {
|
|
u = usr
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if u == nil {
|
|
return nil, &platform.Error{
|
|
Code: platform.ENotFound,
|
|
Msg: "user not found",
|
|
}
|
|
}
|
|
|
|
return u, nil
|
|
}
|
|
|
|
func filterExamplesFn(filter platform.UserFilter) func(u *platform.User) bool {
|
|
if filter.ID != nil {
|
|
return func(u *platform.User) bool {
|
|
return u.ID.Valid() && u.ID == *filter.ID
|
|
}
|
|
}
|
|
|
|
if filter.Name != nil {
|
|
return func(u *platform.User) bool {
|
|
return u.Name == *filter.Name
|
|
}
|
|
}
|
|
|
|
return func(u *platform.User) bool { return true }
|
|
}
|
|
|
|
// FindUsers retrives all examples that match an arbitrary example filter.
|
|
// Filters using ID, or Name should be efficient.
|
|
// Other filters will do a linear scan across all examples searching for a match.
|
|
func (c *ExampleService) FindUsers(ctx context.Context, filter platform.UserFilter, opt ...platform.FindOptions) ([]*platform.User, int, error) {
|
|
op := platform.OpFindUsers
|
|
if filter.ID != nil {
|
|
u, err := c.FindUserByID(ctx, *filter.ID)
|
|
if err != nil {
|
|
return nil, 0, &platform.Error{
|
|
Err: err,
|
|
Op: "kv/" + op,
|
|
}
|
|
}
|
|
|
|
return []*platform.User{u}, 1, nil
|
|
}
|
|
|
|
if filter.Name != nil {
|
|
u, err := c.FindUserByName(ctx, *filter.Name)
|
|
if err != nil {
|
|
return nil, 0, &platform.Error{
|
|
Err: err,
|
|
Op: "kv/" + op,
|
|
}
|
|
}
|
|
|
|
return []*platform.User{u}, 1, nil
|
|
}
|
|
|
|
us := []*platform.User{}
|
|
filterFn := filterExamplesFn(filter)
|
|
err := c.kv.View(func(tx Tx) error {
|
|
return forEachExample(ctx, tx, func(u *platform.User) bool {
|
|
if filterFn(u) {
|
|
us = append(us, u)
|
|
}
|
|
return true
|
|
})
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return us, len(us), nil
|
|
}
|
|
|
|
// CreateUser creates a platform example and sets b.ID.
|
|
func (c *ExampleService) CreateUser(ctx context.Context, u *platform.User) error {
|
|
err := c.kv.Update(func(tx Tx) error {
|
|
unique := c.uniqueExampleName(ctx, tx, u)
|
|
|
|
if !unique {
|
|
// TODO: make standard error
|
|
return &platform.Error{
|
|
Code: platform.EConflict,
|
|
Msg: fmt.Sprintf("user with name %s already exists", u.Name),
|
|
}
|
|
}
|
|
|
|
u.ID = c.idGenerator.ID()
|
|
|
|
return c.putUser(ctx, tx, u)
|
|
})
|
|
|
|
if err != nil {
|
|
return &platform.Error{
|
|
Err: err,
|
|
Op: "kv/" + platform.OpCreateUser,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PutUser will put a example without setting an ID.
|
|
func (c *ExampleService) PutUser(ctx context.Context, u *platform.User) error {
|
|
return c.kv.Update(func(tx Tx) error {
|
|
return c.putUser(ctx, tx, u)
|
|
})
|
|
}
|
|
|
|
func (c *ExampleService) putUser(ctx context.Context, tx Tx, u *platform.User) error {
|
|
v, err := json.Marshal(u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
encodedID, err := u.ID.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
idx, err := tx.Bucket(exampleIndex)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := idx.Put(exampleIndexKey(u.Name), encodedID); err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := tx.Bucket(exampleBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return b.Put(encodedID, v)
|
|
}
|
|
|
|
func exampleIndexKey(n string) []byte {
|
|
return []byte(n)
|
|
}
|
|
|
|
// forEachExample will iterate through all examples while fn returns true.
|
|
func forEachExample(ctx context.Context, tx Tx, fn func(*platform.User) bool) error {
|
|
b, err := tx.Bucket(exampleBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cur, err := b.Cursor()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for k, v := cur.First(); k != nil; k, v = cur.Next() {
|
|
u := &platform.User{}
|
|
if err := json.Unmarshal(v, u); err != nil {
|
|
return err
|
|
}
|
|
if !fn(u) {
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ExampleService) uniqueExampleName(ctx context.Context, tx Tx, u *platform.User) bool {
|
|
idx, err := tx.Bucket(exampleIndex)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if _, err := idx.Get(exampleIndexKey(u.Name)); err == ErrKeyNotFound {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// UpdateUser updates a example according the parameters set on upd.
|
|
func (c *ExampleService) UpdateUser(ctx context.Context, id platform.ID, upd platform.UserUpdate) (*platform.User, error) {
|
|
var u *platform.User
|
|
err := c.kv.Update(func(tx Tx) error {
|
|
usr, err := c.updateUser(ctx, tx, id, upd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u = usr
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, &platform.Error{
|
|
Err: err,
|
|
Op: "kv/" + platform.OpUpdateUser,
|
|
}
|
|
}
|
|
|
|
return u, nil
|
|
}
|
|
|
|
func (c *ExampleService) updateUser(ctx context.Context, tx Tx, id platform.ID, upd platform.UserUpdate) (*platform.User, error) {
|
|
u, err := c.findUserByID(ctx, tx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if upd.Name != nil {
|
|
// Examples are indexed by name and so the example index must be pruned
|
|
// when name is modified.
|
|
idx, err := tx.Bucket(exampleIndex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := idx.Delete(exampleIndexKey(u.Name)); err != nil {
|
|
return nil, err
|
|
}
|
|
u.Name = *upd.Name
|
|
}
|
|
|
|
if err := c.putUser(ctx, tx, u); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return u, nil
|
|
}
|
|
|
|
// DeleteUser deletes a example and prunes it from the index.
|
|
func (c *ExampleService) DeleteUser(ctx context.Context, id platform.ID) error {
|
|
err := c.kv.Update(func(tx Tx) error {
|
|
return c.deleteUser(ctx, tx, id)
|
|
})
|
|
|
|
if err != nil {
|
|
return &platform.Error{
|
|
Op: "kv/" + platform.OpDeleteUser,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ExampleService) deleteUser(ctx context.Context, tx Tx, id platform.ID) error {
|
|
u, err := c.findUserByID(ctx, tx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
encodedID, err := id.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
idx, err := tx.Bucket(exampleIndex)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := idx.Delete(exampleIndexKey(u.Name)); err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := tx.Bucket(exampleBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := b.Delete(encodedID); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|