Skip to content

Commit

Permalink
Refactor optimistic lock api and test (#276)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fs02 committed Mar 25, 2022
1 parent f3a1b01 commit 701b5d7
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 97 deletions.
3 changes: 1 addition & 2 deletions filter_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,7 @@ func Eq(field string, value interface{}) FilterQuery {
}
}

// Eq expression for version lock
func LockVersion(version int) FilterQuery {
func lockVersion(version int) FilterQuery {
return Eq("lock_version", version)
}

Expand Down
4 changes: 2 additions & 2 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ func (r repository) applyMutates(cw contextWrapper, doc *Document, mutation Muta

if version, ok := r.lockVersion(*doc, mutation.Unscoped); ok {
Set("lock_version", version+1).Apply(doc, &mutation)
queries = append(queries, LockVersion(version))
queries = append(queries, lockVersion(version))
defer func() {
if dbErr != nil {
doc.SetValue("lock_version", version)
Expand Down Expand Up @@ -823,7 +823,7 @@ func (r repository) delete(cw contextWrapper, doc *Document, filter FilterQuery,
var filters []Querier = []Querier{filter, mutation.Unscoped}

if version, ok := r.lockVersion(*doc, mutation.Unscoped); ok {
filters = append(filters, LockVersion(version))
filters = append(filters, lockVersion(version))
}

var (
Expand Down
186 changes: 93 additions & 93 deletions repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,48 @@ func TestRepository_Update_softDeleteUnscoped(t *testing.T) {
adapter.AssertExpectations(t)
}

func TestRepository_Update_lockVersion(t *testing.T) {
var (
adapter = &testAdapter{}
repo = New(adapter)
transaction = VersionedTransaction{
Transaction: Transaction{
ID: 1,
Item: "item",
},
LockVersion: 5,
}
unscopedMutates = map[string]Mutate{
"item": Set("item", "new item"),
}
mutates = map[string]Mutate{
"item": unscopedMutates["item"],
"lock_version": Set("lock_version", 6),
}
baseQueries = From("transactions").Where(Eq("id", transaction.ID))
queries = baseQueries.Where(Eq("lock_version", transaction.LockVersion))
)

// update and increment lock
adapter.On("Update", queries, "id", mutates).Return(1, nil).Once()
assert.Nil(t, repo.Update(context.TODO(), &transaction, Set("item", "new item")))

assert.Equal(t, 5+1, transaction.LockVersion)

// try to update with expired lock
transaction.LockVersion = 5
adapter.On("Update", queries, "id", mutates).Return(0, nil).Once()
err := repo.Update(context.TODO(), &transaction, Set("item", "new item"))
assert.ErrorIs(t, err, NotFoundError{})
assert.Equal(t, 5, transaction.LockVersion)

// unscoped
adapter.On("Update", baseQueries.Unscoped(), "id", unscopedMutates).Return(1, nil).Once()
assert.Nil(t, repo.Update(context.TODO(), &transaction, Set("item", "new item"), Unscoped(true)))

adapter.AssertExpectations(t)
}

func TestRepository_Update_notFound(t *testing.T) {
var (
user = User{ID: 1}
Expand Down Expand Up @@ -2679,7 +2721,57 @@ func TestRepository_Delete_softAltDelete(t *testing.T) {
adapter.AssertExpectations(t)
}

func TestRepository_InvalidSoftDeleteFieldTypes(t *testing.T) {
func TestRepository_Delete_lockVersion(t *testing.T) {
var (
adapter = &testAdapter{}
repo = New(adapter)
transaction = VersionedTransaction{
Transaction: Transaction{
ID: 1,
Item: "item",
},
LockVersion: 5,
}
baseQueries = From("transactions").Where(Eq("id", transaction.ID))
queries = baseQueries.Where(Eq("lock_version", transaction.LockVersion))
)

// delete
adapter.On("Delete", queries).Return(1, nil).Once()
assert.Nil(t, repo.Delete(context.TODO(), &transaction))

// unscoped
adapter.On("Delete", baseQueries.Unscoped()).Return(1, nil).Once()
assert.Nil(t, repo.Delete(context.TODO(), &transaction, Unscoped(true)))

adapter.AssertExpectations(t)
}

func TestRepository_Delete_softDeleteWithLockVersion(t *testing.T) {
var (
adapter = &testAdapter{}
repo = New(adapter)
address = SoftDelVersionedTransaction{
Transaction: Transaction{
ID: 1,
},
LockVersion: 5,
Deleted: false,
}
queries = From(address.Table()).Where(Eq("id", address.ID)).Where(Eq("lock_version", address.LockVersion))
mutates = map[string]Mutate{
"deleted": Set("deleted", true),
"lock_version": Inc("lock_version"),
}
)

adapter.On("Update", queries, "", mutates).Return(1, nil).Once()
assert.Nil(t, repo.Delete(context.TODO(), &address))

adapter.AssertExpectations(t)
}

func TestRepository_Delete_invalidFieldType(t *testing.T) {
type InvalidField struct {
ID int
CreatedAt bool
Expand Down Expand Up @@ -3958,95 +4050,3 @@ func TestRepository_Transaction_runtimeError(t *testing.T) {

adapter.AssertExpectations(t)
}

func TestRepository_LockVersion_Update(t *testing.T) {
var (
adapter = &testAdapter{}
repo = New(adapter)
transaction = VersionedTransaction{
Transaction: Transaction{
ID: 1,
Item: "item",
},
LockVersion: 5,
}
unscopedMutates = map[string]Mutate{
"item": Set("item", "new item"),
}
mutates = map[string]Mutate{
"item": unscopedMutates["item"],
"lock_version": Set("lock_version", 6),
}
baseQueries = From("transactions").Where(Eq("id", transaction.ID))
queries = baseQueries.Where(Eq("lock_version", transaction.LockVersion))
)

// update and increment lock
adapter.On("Update", queries, "id", mutates).Return(1, nil).Once()
assert.Nil(t, repo.Update(context.TODO(), &transaction, Set("item", "new item")))

assert.Equal(t, 5+1, transaction.LockVersion)

// try to update with expired lock
transaction.LockVersion = 5
adapter.On("Update", queries, "id", mutates).Return(0, nil).Once()
err := repo.Update(context.TODO(), &transaction, Set("item", "new item"))
assert.ErrorIs(t, err, NotFoundError{})
assert.Equal(t, 5, transaction.LockVersion)

// unscoped
adapter.On("Update", baseQueries.Unscoped(), "id", unscopedMutates).Return(1, nil).Once()
assert.Nil(t, repo.Update(context.TODO(), &transaction, Set("item", "new item"), Unscoped(true)))

adapter.AssertExpectations(t)
}

func TestRepository_LockVersion_Delete(t *testing.T) {
var (
adapter = &testAdapter{}
repo = New(adapter)
transaction = VersionedTransaction{
Transaction: Transaction{
ID: 1,
Item: "item",
},
LockVersion: 5,
}
baseQueries = From("transactions").Where(Eq("id", transaction.ID))
queries = baseQueries.Where(Eq("lock_version", transaction.LockVersion))
)

// delete
adapter.On("Delete", queries).Return(1, nil).Once()
assert.Nil(t, repo.Delete(context.TODO(), &transaction))

// unscoped
adapter.On("Delete", baseQueries.Unscoped()).Return(1, nil).Once()
assert.Nil(t, repo.Delete(context.TODO(), &transaction, Unscoped(true)))

adapter.AssertExpectations(t)
}

func TestRepository_LockVersion_SoftDelete(t *testing.T) {
var (
adapter = &testAdapter{}
repo = New(adapter)
address = SoftDelVersionedTransaction{
Transaction: Transaction{
ID: 1,
},
LockVersion: 5,
Deleted: false,
}
queries = From(address.Table()).Where(Eq("id", address.ID)).Where(Eq("lock_version", address.LockVersion))
mutates = map[string]Mutate{
"deleted": Set("deleted", true),
"lock_version": Inc("lock_version"),
}
)

adapter.On("Update", queries, "", mutates).Return(1, nil).Once()
assert.Nil(t, repo.Delete(context.TODO(), &address))

adapter.AssertExpectations(t)
}

0 comments on commit 701b5d7

Please sign in to comment.