如何定义自定义的 SQL 类型枚举字段?

huangapple go评论79阅读模式
英文:

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,则不需要实现ValuerScanner接口。如果是这样的话,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.

huangapple
  • 本文由 发表于 2023年5月4日 14:47:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76170294.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定