Skip to content

Commit

Permalink
demonstrate snapshot reads for memdb
Browse files Browse the repository at this point in the history
in order to demonstrate snapshot reads
the caveat write operation had to be adjusted
to support upserts. Thus it's no longer
returning an error on duplicate caveats
  • Loading branch information
vroldanbet committed Sep 20, 2022
1 parent b91892c commit bb5f08b
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 15 deletions.
21 changes: 14 additions & 7 deletions internal/datastore/memdb/caveat.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package memdb

import (
"errors"
"fmt"
"time"

"github.com/authzed/spicedb/pkg/datastore"
Expand Down Expand Up @@ -34,7 +33,7 @@ func (r *memdbReader) ReadCaveatByName(name string) (*core.Caveat, error) {
if err != nil {
return nil, err
}
return r.readCaveatByName(tx, name)
return r.readUnwrappedCaveatByName(tx, name)
}

func (r *memdbReader) ReadCaveatByID(ID datastore.CaveatID) (*core.Caveat, error) {
Expand All @@ -60,15 +59,22 @@ func (r *memdbReader) readCaveatByID(tx *memdb.Txn, ID datastore.CaveatID) (*cor
return c.Unwrap(), nil
}

func (r *memdbReader) readCaveatByName(tx *memdb.Txn, name string) (*core.Caveat, error) {
func (r *memdbReader) readCaveatByName(tx *memdb.Txn, name string) (*caveat, error) {
found, err := tx.First(tableCaveats, indexName, name)
if err != nil {
return nil, err
}
if found == nil {
return nil, datastore.NewCaveatNameNotFoundErr(name)
}
c := found.(*caveat)
return found.(*caveat), nil
}

func (r *memdbReader) readUnwrappedCaveatByName(tx *memdb.Txn, name string) (*core.Caveat, error) {
c, err := r.readCaveatByName(tx, name)
if err != nil {
return nil, err
}
return c.Unwrap(), nil
}

Expand All @@ -91,14 +97,15 @@ func (rwt *memdbReadWriteTx) writeCaveat(tx *memdb.Txn, caveats []*core.Caveat)
name: coreCaveat.Name,
expression: coreCaveat.Expression,
}
// MemDB does not enforce uniqueness on indices marked as unique
// https://github.com/hashicorp/go-memdb/issues/7
// in order to implement upserts we need to determine the ID of the previously
// stored caveat
found, err := rwt.readCaveatByName(tx, coreCaveat.Name)
if err != nil && !errors.As(err, &datastore.ErrCaveatNameNotFound{}) {
return nil, err
}
if found != nil {
return nil, fmt.Errorf("duplicated caveat with name %s", coreCaveat.Name)
id = found.id
c.id = id
}
if err = tx.Insert(tableCaveats, &c); err != nil {
return nil, err
Expand Down
57 changes: 50 additions & 7 deletions internal/datastore/memdb/caveat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,20 @@ func TestWriteReadCaveat(t *testing.T) {
ds, err := NewMemdbDatastore(0, 1*time.Hour, 1*time.Hour)
req.NoError(err)

// Fails to write dupes in the same transaction
// Dupes in same transaction are treated as upserts
coreCaveat := createCoreCaveat(t)
ctx := context.Background()
_, _, err = writeCaveat(ctx, ds, coreCaveat, coreCaveat)
req.Error(err)
_, ids, err := writeCaveats(ctx, ds, coreCaveat, coreCaveat)
req.NoError(err)
req.Equal(ids[0], ids[1]) // dupe caveats generate same IDs

// Succeeds writing a caveat
rev, ID, err := writeCaveat(ctx, ds, coreCaveat)
req.NoError(err)

// Fails to write caveat with the same name in different tx
// Writing same named caveat in different tx is treated as upsert
_, _, err = writeCaveat(ctx, ds, coreCaveat)
req.Error(err)
req.NoError(err)

// The caveat can be looked up by name
cr, ok := ds.SnapshotReader(rev).(datastore.CaveatReader)
Expand Down Expand Up @@ -89,6 +90,40 @@ func TestWriteCaveatedTuple(t *testing.T) {
req.NoError(err)
}

func TestCaveatSnapshotReads(t *testing.T) {
req := require.New(t)

ds, err := NewMemdbDatastore(0, 1*time.Hour, 1*time.Hour)
req.NoError(err)

// Write an initial caveat
coreCaveat := createCoreCaveat(t)
ctx := context.Background()
oldRev, oldID, err := writeCaveat(ctx, ds, coreCaveat)
req.NoError(err)

// Modify caveat and update
oldExpression := coreCaveat.Expression
newExpression := []byte{0x0a}
coreCaveat.Expression = newExpression
newRev, newID, err := writeCaveat(ctx, ds, coreCaveat)
req.NoError(err)

// check most recent revision
cr, ok := ds.SnapshotReader(newRev).(datastore.CaveatReader)
req.True(ok, "expected a CaveatStorer value")
cv, err := cr.ReadCaveatByID(oldID)
req.NoError(err)
req.Equal(newExpression, cv.Expression)

// check previous revision
cr, ok = ds.SnapshotReader(oldRev).(datastore.CaveatReader)
req.True(ok, "expected a CaveatStorer value")
cv, err = cr.ReadCaveatByID(newID)
req.NoError(err)
req.Equal(oldExpression, cv.Expression)
}

func createTestCaveatedTuple(t *testing.T, tplString string, id datastore.CaveatID) *core.RelationTuple {
tpl := tuple.MustParse(tplString)
st, err := structpb.NewStruct(map[string]interface{}{"a": 1, "b": "test"})
Expand All @@ -101,7 +136,7 @@ func createTestCaveatedTuple(t *testing.T, tplString string, id datastore.Caveat
return tpl
}

func writeCaveat(ctx context.Context, ds datastore.Datastore, coreCaveat ...*core.Caveat) (datastore.Revision, datastore.CaveatID, error) {
func writeCaveats(ctx context.Context, ds datastore.Datastore, coreCaveat ...*core.Caveat) (datastore.Revision, []datastore.CaveatID, error) {
var IDs []datastore.CaveatID
rev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, tx datastore.ReadWriteTransaction) error {
cs, ok := tx.(datastore.CaveatStorer)
Expand All @@ -112,10 +147,18 @@ func writeCaveat(ctx context.Context, ds datastore.Datastore, coreCaveat ...*cor
IDs, err = cs.WriteCaveats(coreCaveat)
return err
})
if err != nil {
return datastore.NoRevision, nil, err
}
return rev, IDs, err
}

func writeCaveat(ctx context.Context, ds datastore.Datastore, coreCaveat *core.Caveat) (datastore.Revision, datastore.CaveatID, error) {
rev, ids, err := writeCaveats(ctx, ds, coreCaveat)
if err != nil {
return datastore.NoRevision, 0, err
}
return rev, IDs[0], err
return rev, ids[0], nil
}

func createCoreCaveat(t *testing.T) *core.Caveat {
Expand Down
1 change: 0 additions & 1 deletion pkg/datastore/caveat.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ type CaveatID uint64

// CaveatReader offers read operations for caveats
type CaveatReader interface {

// ReadCaveatByName returns a caveat with the provided name
ReadCaveatByName(name string) (*core.Caveat, error)

Expand Down

0 comments on commit bb5f08b

Please sign in to comment.