diff --git a/filter_query.go b/filter_query.go index b3fdef75..5fcc62c7 100644 --- a/filter_query.go +++ b/filter_query.go @@ -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) } diff --git a/repository.go b/repository.go index 98253931..f4a4102d 100644 --- a/repository.go +++ b/repository.go @@ -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) @@ -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 ( diff --git a/repository_test.go b/repository_test.go index 00150cce..ed7b1e66 100644 --- a/repository_test.go +++ b/repository_test.go @@ -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} @@ -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 @@ -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) -}