Giter Club home page Giter Club logo

optimisticlock's People

Contributors

a631807682 avatar dependabot[bot] avatar icpd avatar jinzhu avatar mnussbaum avatar moredure avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

optimisticlock's Issues

希望能支持gorm-gen。

在使用gorm-gen时,出现panic

生成语句:

g.GenerateModel("user", optVersionType := gen.FieldType("version", "optimisticlock.Version"))

生成的模型:

type User struct {
	ID            int64                  `gorm:"column:id;primaryKey" json:"id"`                                       // 主键
// 忽略其它字段
	Version       optimisticlock.Version `gorm:"column:version;not null" json:"version"`                               // 乐观锁
}

执行操作:

q.User.WithContext(ctx).Create(&model.User{ID: 123})

出现错误:

panic: reflect: call of reflect.Value.Field on slice Value [recovered]
panic: reflect: call of reflect.Value.Field on slice Value

panic({0x13f5d40, 0xc000438060})
	C:/Users/NorthLan/.g/go/src/runtime/panic.go:838 +0x207
reflect.Value.Field({0x13d3160?, 0xc000438048?, 0x13b7da5?}, 0x0?)
	C:/Users/NorthLan/.g/go/src/reflect/value.go:1220 +0xef
gorm.io/gorm/schema.(*Field).setupValuerAndSetter.func1({0xc000132c00?, 0x13?}, {0x13d3160?, 0xc000438048?, 0xf1be5d?})
	C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/schema/field.go:446 +0x65
gorm.io/plugin/optimisticlock.VersionCreateClause.ModifyStatement({0x20?}, 0xc00022d340)
	C:/Users/NorthLan/go/pkg/mod/gorm.io/plugin/optimisticlock@v1.0.7/version.go:68 +0x4f
gorm.io/gorm.(*Statement).AddClause(0xc00022d340, {0x155ab08, 0xc000413680})
	C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/statement.go:257 +0x46
gorm.io/gorm/callbacks.Create.func1(0xc000432330)
	C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/callbacks/create.go:47 +0xf88
gorm.io/gorm.(*processor).Execute(0xc0001ffae0, 0xc00040fb28?)

	C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/callbacks.go:130 +0x433
gorm.io/gorm.(*DB).Create(0x144e1c0?, {0x13d3160?, 0xc000438048})
	C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/finisher_api.go:24 +0xa5
gorm.io/gen.(*DO).Save(0xc00040fc88, {0x13d3160, 0xc000438048})
	C:/Users/NorthLan/go/pkg/mod/gorm.io/gen@v0.3.8/do.go:553 +0xba

看情况应该是 version.go 中这个方法的异常,大概是通过反射寻找非零值的 Version

func (v VersionCreateClause) ModifyStatement(stmt *gorm.Statement) {
	var value int64 = 1
	if val, zero := v.Field.ValueOf(stmt.Context, stmt.ReflectValue); !zero {
		if version, ok := val.(Version); ok {
			value = version.Int64
		}
	}
	stmt.SetColumn(v.Field.DBName, value)
}

摸一摸具体原因可能是:

因为gorm-gen的 Create 方法是传递的可变参数,stmt.ReflectValue 的 Kind是23(slice)而非25(struct)出现的错误。

// gorm-gen生成的Create方法
// u.DO.Create底层调用的还是 gorm.DB 的 Create 方法
func (u userDo) Create(values ...*model.User) error {
	if len(values) == 0 {
		return nil
	}
	return u.DO.Create(values)
}

特别地,如果使用 gorm.DB 的 Create 就不会存在问题,例: db.Create(&user)

特别地2,如果使用 gorm.DB 的 Create ,但是传入slice,一样也会出现问题。例:db.Create(&[]model.User{user})

由此可得,修改修改 ModifyStatement 应该就好了。

是否可以给出选项, 跳过乐观锁

场景

有些字段是不存在并发冲突风险的, 开发者会直接使用 map 来更新.

result = db.Model(&account).Updates(map[string]interface{}{"video_profit": account.VideoProfit})

但是目前的机制, 会自动增加 version 到 where 中导致更新失败

诉求

希望提供额外的选项, 例如说 IgnoreLockVersion, 可以在使用 map 或者 struct 更新时, 忽略乐观锁的限制, 例如 where 中不再与偶 version 的判断

事务内多次 update, 生成的 SQL 错误

func TestUpdateWithLock(t *testing.T) {
	db := initializers.DB.WithContext(context.Background())

	err := db.Transaction(func(tx *gorm.DB) error {
		account := models.Account{}

		tx.Model(&account).Where("id = ?", 1).First(&account)

		account.CostDiamond(decimal.NewFromInt(1))
		if result := tx.Updates(&account) ; result.RowsAffected == 0 {
		    return errors.New("更新用户账户失败")
		}

		account.CostGold(decimal.NewFromInt(1))
		if result := tx.Updates(&account) ; result.RowsAffected == 0 {
		    return errors.New("更新用户账户失败")
		}

		return nil
	})
	if err != nil {
		fmt.Println("error", err.Error())
		return
	}
}

生成两条 SQL

UPDATE `accounts` SET `version`=`version`+1 WHERE `accounts`.`deleted_at` IS NULL AND `accounts`.`version` = 2 AND `id` = 1	{rows": 1}

UPDATE `accounts` SET `version`=`version`+1 WHERE `accounts`.`deleted_at` IS NULL AND `accounts`.`version` = 2 AND `id` = 1	{"rows": 0}

按照示例执行后结果不符合预期

DB.Model(&user).Updates(&User{Age: 18, Version: optimisticlock.Version{Int64: 1}})
// UPDATE users SET age=18,version=version+1 WHERE users.version = 3 AND id = 1

上述代码的执行结果是:
UPDATE users SET age=18,version=version+1

Version not being set when primary key is set?

GORM Playground Link

PR: #22 This reproduces the problem TestPostgres passes and TestPostgres_FailedCase fails.

Description

I took your unit test and just added my own primary key. When I try to user.ID = "test1" add this line to set a primary key, the test values because the version is set to 0. When i remove that line, the test pass with the version set to 1.

There is probably something that i'm not understanding with this...

type User struct {
	ID      string `gorm:"primarykey"`
	Name    string
	Age     uint
	Version optimisticlock.Version
}

type User struct {
	ID        string `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	Name      string
	Age       uint
	Version   optimisticlock.Version
}

func TestPostgres(t *testing.T) {
	DB, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
	require.Nil(t, err)
	_ = DB.Migrator().DropTable(&User{})
	_ = DB.AutoMigrate(&User{})

	user := User{Name: "bob", Age: 20}
	DB.Save(&user)
	require.Equal(t, "bob", user.Name)
	require.Equal(t, uint(20), user.Age)
	require.Equal(t, int64(1), user.Version.Int64)

	rows := DB.Model(&user).Update("age", 18).RowsAffected
	require.Equal(t, int64(1), rows)
	require.Nil(t, DB.First(&user).Error)
	require.Equal(t, int64(2), user.Version.Int64)
	require.Equal(t, uint(18), user.Age)
}

func TestPostgres_FailedCase(t *testing.T) {
	DB, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
	require.Nil(t, err)
	_ = DB.Migrator().DropTable(&User{})
	_ = DB.AutoMigrate(&User{})

	user := User{Name: "bob", Age: 20, ID: "test"}
	DB.Save(&user)
	require.Equal(t, "bob", user.Name)
	require.Equal(t, uint(20), user.Age)
	require.Equal(t, int64(1), user.Version.Int64)

	rows := DB.Model(&user).Update("age", 18).RowsAffected
	require.Equal(t, int64(1), rows)
	require.Nil(t, DB.First(&user).Error)
	require.Equal(t, int64(2), user.Version.Int64)
	require.Equal(t, uint(18), user.Age)
}

In the debugger, it looks like the save went through but version is 0.
image

When primary key is not set, you can see it set to version 1
image

Updates 失效

简化后的场景

model 定义

// Account 用户钱包
type Account struct {
	gorm.Model

	UserID *uint
	User   *User

	GoldAmount            decimal.Decimal        `gorm:"type:decimal(20,2)"`  //金币余额

	Version               optimisticlock.Version `gorm:"default:0"`
}

demo

var account Account
db.First(&account)

account.CostGold(decimal.NewFromFloat(100))
db.Updates(&account)

问题

未引入 Version 字段时, db.Updates(&account) 工作起来没问题, 也符合 DDD 的常见写法; 引入后报错:

sql: UPDATE `accounts` SET `gold_amount`='3400',`Model`='{47 2022-03-05 12:40:14.574 +0800 CST 2022-03-09 01:20:14.138 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}}',`version`=`version`+1,`updated_at`='2022-03-09 01:20:14.182' WHERE `accounts`.`deleted_at` IS NULL AND `id` = 47

"error": "sql: converting argument $7 type: unsupported type gorm.Model, a struct"

插件会导致updateat不更新

加上本插件字段后,updateat字段的值不会自动变化
UPDATE s SET created_at="2022-09-02 15:42:12.845",id=1,name="name",updated_at="2022-09-02 15:42:12.845",version=version+1 WHERE s.version = 1 AND id = 1

去掉后正常
UPDATE s SET name="name",created_at="2022-09-02 15:44:39.756",updated_at="2022-09-02 15:44:40.763" WHERE id = 1

Optimistic lock for associations when FullSaveAssociations is enabled

Your Question

When FullSaveAssociations is enabled and an association is updated, the generated query is not modified by this plugin and therefore locking is not working even though version field is available for that association.
Is there any possible solution for this? (except disabling FullSaveAssociations)

The document you expected this should be explained

Expected answer

making the optimisticlock plugin work with gorm v1.22.5

The version column is not incrementing when using the optimistic lock plugin with gorm v1.22.5. Below is the generated query.

UPDATE "" SET "name"='name-1',"created_timestamp"='2022-11-30 12:56:05.62',"updated_timestamp"='2022-11-30 14:49:04.531',"sync_timestamp"='0000-00-00 00:00:00',"created_by"='',"updated_by"='',"o_version"=1, "status"='Available' WHERE "uuid" = '327250f2-c77f-4996-b8a1-7ec7c76c1e0a' AND ""."o_version" = 1

"o_version" is name of the column used for optimistic locking.

type Common struct {
UUID uuid.UUID gorm:"primaryKey" json:"uuid,omitempty"
Name string gorm:"not null;unique" json:"name,omitempty"
OVersion optimisticlock.Version json:"oVersion,omitempty"
}

As you can see in the generated query the updated clause contains "o_version"=1, but according to implementation it should contain an SQL query of form "o_version"="o_version"+1 for optimistic locking to work properly. Your help will be greatly appreciated.

ERROR: syntax error at or near \"=`\" (SQLSTATE 42601)

GORM Playground Link

Description

The following error is reported when using postgres

ERROR: syntax error at or near \"=`\" (SQLSTATE 42601)

The sql as follows:

UPDATE
	instances
SET
	stage = 'Pending',
	version = `version` + 1,
	updated_at = '2022-03-28 18:15:24.572'
WHERE
	AND deleted_at IS NULL
	AND id = '123';

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.