英文:
how to define custom sql type enum field
问题
我想定义一个自定义的 SQL 类型 enum,代码如下:
package db
import (
	"database/sql/driver"
	"github.com/spf13/cast"
)
type Enum int64
func (e Enum) Value() (driver.Value, error) {
	return int64(e), nil
}
func (e *Enum) Scan(v any) error {
	*e = Enum(cast.ToInt64(v))
	return nil
}
我想要使用以下代码:
package db_test
import (
	"db"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"testing"
)
type StatusEnum = db.Enum
const (
	StatusEnumEnable StatusEnum = iota
	StatusEnumDisable
)
var StatusEnumMap = map[StatusEnum]string{
	StatusEnumEnable:  "Enable",
	StatusEnumDisable: "Disable",
}
type module struct {
	Status StatusEnum
}
func TestEnum(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"))
	db.AutoMigrate(&module{})
	db.Model(&module{}).Create(&module{Status: StatusEnumDisable})
	var out *module
	db.Debug().Model(&module{}).Where("status=?", StatusEnumDisable).First(&out)
	t.Log(out)
	t.Log(StatusEnumMap[out.Status])
}
现在我在需要字符串格式时使用 StatusEnumMap[EnumValue]。我认为 value.String() 更加优雅简洁,而且 fmt 格式化也可以打印字符串而不是数字。
你有什么更好的实现方法吗?谢谢你的建议。
上面的问题已经由 @Zeke Lu 回答了。
另一个小问题是,如果我不使用 Valuer 并删除 String 方法,那么 SQL 条件 status=1 将变成 status="1",为什么会这样?如果 MySQL 中存在类型不一致,查询性能会受到影响吗?
第二个问题只是 GORM 的调试信息。实际执行的 SQL 是 status=1。请参考 gorm-issue。
英文:
I want to define custom sql type enum like this:
package db
import (
	"database/sql/driver"
	"github.com/spf13/cast"
)
type Enum int64
func (e Enum) Value() (driver.Value, error) {
	return int64(e), nil
}
func (e *Enum) Scan(v any) error {
	*e = Enum(cast.ToInt64(v))
	return nil
}
And I want use the following:
package db_test
import (
	"db"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"testing"
)
type StatusEnum = db.Enum
const (
	StatusEnumEnable StatusEnum = iota
	StatusEnumDisable
)
// this is error because this type alias can not define new method
//func (e *StatusEnum) String() string{
//	if v,ok := StatusEnumMap[*e];ok{
//		return v
//	}
//	return "unknown"
//}
var StatusEnumMap = map[StatusEnum]string{
	StatusEnumEnable:  "Enable",
	StatusEnumDisable: "Disable",
}
type module struct {
	Status StatusEnum
}
func TestEnum(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"))
	db.AutoMigrate(&module{})
	db.Model(&module{}).Create(&module{Status: StatusEnumDisable})
	var out *module
	// SELECT * FROM `modules` WHERE status=1 ORDER BY `modules`.`status` LIMIT 1
	db.Debug().Model(&module{}).Where("status=?", StatusEnumDisable).First(&out)
	t.Log(out)
	t.Log(StatusEnumMap[out.Status])
	//t.Log(out.Status.String())
}
Now I'm using it like this StatusEnumMap[EnumValue] when I want the string format. I think value.String() is more elegant and simple. fmt formatting can also print strings instead of numbers.
How can I better implement this feature? Thank you for your suggestions.
The above question has been answered by @Zeke Lu.
Another little problem, If I did not used Valuer and delete the String method, the sql condition status=1 will be status="1", why? Whether the query performance of sql will be affected if the type inconsistency in mysql.
The second problem:just is gorm debug info. Actual execute sql is status=1. See gorm-issue
答案1
得分: 1
似乎如果底层类型是int64,则不需要实现Valuer和Scanner接口。如果是这样的话,Enum类型就不需要了。下面的示例在没有Enum的情况下工作:
注意:我稍微修改了你的示例,将StatusEnum类型上的Stringer接口实现改为了*StatusEnum。
package db_test
import (
	"testing"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)
type StatusEnum int64
const (
	StatusEnumEnable StatusEnum = iota
	StatusEnumDisable
)
var StatusEnumMap = map[StatusEnum]string{
	StatusEnumEnable:  "Enable",
	StatusEnumDisable: "Disable",
}
func (e StatusEnum) String() string {
	if v, ok := StatusEnumMap[e]; ok {
		return v
	}
	return "unknown"
}
type module struct {
	Status StatusEnum
}
func TestEnum(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"))
	db.AutoMigrate(&module{})
	db.Model(&module{}).Create(&module{Status: StatusEnumDisable})
	var out *module
	// SELECT * FROM `modules` WHERE status=1 ORDER BY `modules`.`status` LIMIT 1
	db.Debug().Model(&module{}).Where("status=?", StatusEnumDisable).First(&out)
	t.Log(out)
	t.Logf("status value: %d, status string: %s", out.Status, out.Status)
}
输出结果为:
&{Disable}
status value: 1, status string: Disable
请注意,第一个输出现在是&{Disable},之前是&{1}。
英文:
It seems that you don't need to implement the Valuer and Scanner interfaces if the underlying type is int64. If that's true, the type Enum is not needed. The following example works without Enum:
Note: I modified your demo slightly to implement the Stringer interface  on type StatusEnum instead of *StatusEnum.
package db_test
import (
	"testing"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)
type StatusEnum int64
const (
	StatusEnumEnable StatusEnum = iota
	StatusEnumDisable
)
var StatusEnumMap = map[StatusEnum]string{
	StatusEnumEnable:  "Enable",
	StatusEnumDisable: "Disable",
}
func (e StatusEnum) String() string {
	if v, ok := StatusEnumMap[e]; ok {
		return v
	}
	return "unknown"
}
type module struct {
	Status StatusEnum
}
func TestEnum(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"))
	db.AutoMigrate(&module{})
	db.Model(&module{}).Create(&module{Status: StatusEnumDisable})
	var out *module
	// SELECT * FROM `modules` WHERE status=1 ORDER BY `modules`.`status` LIMIT 1
	db.Debug().Model(&module{}).Where("status=?", StatusEnumDisable).First(&out)
	t.Log(out)
	t.Logf("status value: %d, status string: %s", out.Status, out.Status)
}
The output is:
&{Disable}
status value: 1, status string: Disable
Please note that the first one is &{Disable} now. It's &{1} before.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论