Skip to content

Commit

Permalink
First steps in IdentityMap implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Sushkov committed Mar 4, 2024
1 parent 32485fb commit fbf36ef
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 0 deletions.
107 changes: 107 additions & 0 deletions grade/internal/infrastructure/seedwork/session/identity_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package session

import "errors"

type IsolationLevel int

type NonexistentObject struct{}

var ErrNonexistentObject = errors.New("")

const (
ReadUncommittedLevel IsolationLevel = iota
ReadCommittedLevel
RepeatableReadsLevel
SerializableLevel
)

type IsolationStrategy[K any] interface {
add(key K, value any) error
get(key K) (any, error)
has(key K) (bool, error)
}

type SerializableStrategyImpl[K comparable] struct {
identityMap *IdentityMapImpl[K]

Check failure on line 25 in grade/internal/infrastructure/seedwork/session/identity_map.go

View workflow job for this annotation

GitHub Actions / lint

`identityMap` is unused (structcheck)
}

func (s *SerializableStrategyImpl[K]) add(key K, value any) error {
if value == nil {
value = NonexistentObject{}
}

s.identityMap.doAdd(key, value)
return nil
}

func (s *SerializableStrategyImpl[K]) get(key K) (any, error) {

entity := s.identityMap.doGet(key)
if _, ok := entity.(NonexistentObject); ok || entity == nil {
return nil, ErrNonexistentObject
}

return entity, nil
}

func (s *SerializableStrategyImpl[K]) has(key K) (bool, error) {
return s.identityMap.doHas(key), nil
}

type IdentityMapImpl[K comparable] struct {
alive map[K]any
strategy IsolationStrategy[K]
}

func NewIdentityMap[K comparable](isolation IsolationLevel) IdentityMap[K] {
identity := &IdentityMapImpl[K]{
alive: map[K]any{},
}

identity.SetIsolationLevel(isolation)
return identity
}

func (i *IdentityMapImpl[K]) Get(key K) (any, error) {
return i.strategy.get(key)
}

func (i *IdentityMapImpl[K]) Add(key K, value any) error {
return i.strategy.add(key, value)
}

func (i *IdentityMapImpl[K]) Has(key K) (bool, error) {
return i.strategy.has(key)
}

func (i *IdentityMapImpl[K]) Clear() {
i.alive = map[K]any{}
}

func (i *IdentityMapImpl[K]) Remove(key K) {
if _, found := i.alive[key]; !found {
return
}

delete(i.alive, key)
}

func (i *IdentityMapImpl[K]) SetIsolationLevel(isolation IsolationLevel) {
switch isolation {
case SerializableLevel:
i.strategy = &SerializableStrategyImpl[K]{i}
}
}

func (i *IdentityMapImpl[K]) doAdd(key K, value any) {
i.alive[key] = value
}

func (i *IdentityMapImpl[K]) doGet(key K) any {
return i.alive[key]
}

func (i *IdentityMapImpl[K]) doHas(key K) bool {
_, found := i.alive[key]
return found
}
106 changes: 106 additions & 0 deletions grade/internal/infrastructure/seedwork/session/identity_map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package session

import (
"testing"

"github.com/stretchr/testify/assert"
)

type Model struct {
pk int
}

func TestIdentityMap(t *testing.T) {

tests := []struct {
name string
testCase func(t *testing.T)
}{
{
name: "Test IdentityMap with Serializable Level",
testCase: func(t *testing.T) {
idMap := NewIdentityMap[int](SerializableLevel)

model := Model{3}

err := idMap.Add(model.pk, model)
assert.NoError(t, err)

exists, err := idMap.Has(model.pk)
assert.NoError(t, err)
assert.Equal(t, true, exists)

result, err := idMap.Get(model.pk)
assert.NoError(t, err)

assert.Equal(t, model, result)

_, err = idMap.Get(10)
assert.Equal(t, ErrNonexistentObject, err)

err = idMap.Add(10, nil)
assert.NoError(t, err)

_, err = idMap.Get(10)
assert.Equal(t, ErrNonexistentObject, err)
},
},

{
name: "Test IdentityMap object removing",
testCase: func(t *testing.T) {
idMap := NewIdentityMap[int](SerializableLevel)

_ = idMap.Add(3, Model{3})
_ = idMap.Add(5, Model{5})

idMap.Remove(3)
idMap.Remove(1) // remove non-exists

_, err := idMap.Get(3)
assert.Equal(t, ErrNonexistentObject, err)

_, err = idMap.Get(5)
assert.NoError(t, err)
},
},

{
name: "Test IdentityMap clearing",
testCase: func(t *testing.T) {
idMap := NewIdentityMap[int](SerializableLevel)

models := []Model{
Model{3},
Model{5},
Model{15},
}

for _, model := range models {
err := idMap.Add(model.pk, model)
assert.NoError(t, err)
}

for _, model := range models {
_, err := idMap.Get(model.pk)
assert.NoError(t, err)
}

idMap.Clear()

for _, model := range models {
_, err := idMap.Get(model.pk)
assert.Equal(t, ErrNonexistentObject, err)
}
},
},
}

t.Parallel()
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
tt.testCase(t)
})
}
}
10 changes: 10 additions & 0 deletions grade/internal/infrastructure/seedwork/session/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,13 @@ type DeferredDbSession interface {
DeferredDbSessionQuerier
DeferredDbSessionSingleQuerier
}

type IdentityMap[K comparable] interface {
Get(key K) (any, error)
Add(key K, value any) error
Has(key K) (bool, error)
Remove(key K)
Clear()

SetIsolationLevel(isolation IsolationLevel)
}

0 comments on commit fbf36ef

Please sign in to comment.