GORM 提供了少量接口,使用户能够为 GORM 定义支持的数据类型,这里以 json 为例

实现自定义数据类型

Scanner / Valuer

自定义的数据类型必须实现 ScannerValuer 接口,以便让 GORM 知道如何将该类型接收、保存到数据库

例如:

  1. type JSON json.RawMessage
  2. // 实现 sql.Scanner 接口,Scan 将 value 扫描至 Jsonb
  3. func (j *JSON) Scan(value interface{}) error {
  4. bytes, ok := value.([]byte)
  5. if !ok {
  6. return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
  7. }
  8. result := json.RawMessage{}
  9. err := json.Unmarshal(bytes, &result)
  10. *j = JSON(result)
  11. return err
  12. }
  13. // 实现 driver.Valuer 接口,Value 返回 json value
  14. func (j JSON) Value() (driver.Value, error) {
  15. if len(j) == 0 {
  16. return nil, nil
  17. }
  18. return json.RawMessage(j).MarshalJSON()
  19. }

有许多第三方包实现了 Scanner/Valuer 接口,可与 GORM 一起使用,例如:

  1. import (
  2. "github.com/google/uuid"
  3. "github.com/lib/pq"
  4. )
  5. type Post struct {
  6. ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
  7. Title string
  8. Tags pq.StringArray `gorm:"type:text[]"`
  9. }

GormDataTypeInterface

GORM 会从 type 标签 中读取字段的数据库类型,如果找不到,则会检查该结构体是否实现了 GormDBDataTypeInterfaceGormDataTypeInterface 接口,然后使用接口返回值作为数据类型

  1. type GormDataTypeInterface interface {
  2. GormDataType() string
  3. }
  4. type GormDBDataTypeInterface interface {
  5. GormDBDataType(*gorm.DB, *schema.Field) string
  6. }

GormDataType 的结果用于生成通用数据类型,也可以通过 schema.FieldDataType 字段得到。这在 编写插件 或者 hook 时可能会有用,例如:

  1. func (JSON) GormDataType() string {
  2. return "json"
  3. }
  4. type User struct {
  5. Attrs JSON
  6. }
  7. func (user User) BeforeCreate(tx *gorm.DB) {
  8. field := tx.Statement.Schema.LookUpField("Attrs")
  9. if field.DataType == "json" {
  10. // 做点什么...
  11. }
  12. }

在迁移时,GormDBDataType 通常会为当前驱动返回恰当的数据类型,例如:

  1. func (JSON) GormDBDataType(db *gorm.DB, field *schema.Field) string {
  2. // 使用 field.Tag、field.TagSettings 获取字段的 tag
  3. // 查看 https://github.com/go-gorm/gorm/blob/master/schema/field.go 获取全部的选项
  4. // 根据不同的数据库驱动返回不同的数据类型
  5. switch db.Dialector.Name() {
  6. case "mysql", "sqlite":
  7. return "JSON"
  8. case "postgres":
  9. return "JSONB"
  10. }
  11. return ""
  12. }

如果 struct 没有实现 GormDBDataTypeInterfaceGormDataTypeInterface 接口,GORM 会根据 struct 第一个字段推测其数据类型,例如:会为 NullString 使用 string

  1. type NullString struct {
  2. String string // 使用第一个字段的数据类型
  3. Valid bool
  4. }
  5. type User struct {
  6. Name NullString // 数据类型会是 string
  7. }

GormValuerInterface

GORM 提供了 GormValuerInterface 接口,支持使用 SQL 表达式或基于 context 的值进行 create/update,例如:

  1. // GORM Valuer interface
  2. type GormValuerInterface interface {
  3. GormValue(ctx context.Context, db *gorm.DB) clause.Expr
  4. }

使用 SQL 表达式进行 Create/Update

  1. type Location struct {
  2. X, Y int
  3. }
  4. func (loc Location) GormDataType() string {
  5. return "geometry"
  6. }
  7. func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
  8. return clause.Expr{
  9. SQL: "ST_PointFromText(?)",
  10. Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
  11. }
  12. }
  13. // Scan 方法实现了 sql.Scanner 接口
  14. func (loc *Location) Scan(v interface{}) error {
  15. // Scan a value into struct from database driver
  16. }
  17. type User struct {
  18. ID int
  19. Name string
  20. Location Location
  21. }
  22. db.Create(&User{
  23. Name: "jinzhu",
  24. Location: Location{X: 100, Y: 100},
  25. })
  26. // INSERT INTO `users` (`name`,`point`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))
  27. db.Model(&User{ID: 1}).Updates(User{
  28. Name: "jinzhu",
  29. Location: Location{X: 100, Y: 100},
  30. })
  31. // UPDATE `user_with_points` SET `name`="jinzhu",`location`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1

你也可以根据 SQL 表达式进行 create/update,查看 Create From SQL ExprUpdate with SQL Expression 获取详情

基于 Context 的值

如果你想创建或更新一个依赖于当前 context 的值,你也可以实现 GormValuerInterface 接口,例如:

  1. type EncryptedString struct {
  2. Value string
  3. }
  4. func (es EncryptedString) GormValue(ctx context.Context, db *gorm.DB) (expr clause.Expr) {
  5. if encryptionKey, ok := ctx.Value("TenantEncryptionKey").(string); ok {
  6. return clause.Expr{SQL: "?", Vars: []interface{}{Encrypt(es.Value, encryptionKey)}}
  7. } else {
  8. db.AddError(errors.New("invalid encryption key"))
  9. }
  10. return
  11. }

条件表达式

如果你想构建一些查询 helper,你可以让 struct 实现 clause.Expression 接口:

  1. type Expression interface {
  2. Build(builder Builder)
  3. }

查看 JSONSQL Builder 获取详情,下面是一个示例:

  1. // 根据 Clause Expression 生成 SQL
  2. db.Find(&user, datatypes.JSONQuery("attributes").HasKey("role"))
  3. db.Find(&user, datatypes.JSONQuery("attributes").HasKey("orgs", "orga"))
  4. // MySQL
  5. // SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.role') IS NOT NULL
  6. // SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.orgs.orga') IS NOT NULL
  7. // PostgreSQL
  8. // SELECT * FROM "user" WHERE "attributes"::jsonb ? 'role'
  9. // SELECT * FROM "user" WHERE "attributes"::jsonb -> 'orgs' ? 'orga'
  10. db.Find(&user, datatypes.JSONQuery("attributes").Equals("jinzhu", "name"))
  11. // MySQL
  12. // SELECT * FROM `user` WHERE JSON_EXTRACT(`attributes`, '$.name') = "jinzhu"
  13. // PostgreSQL
  14. // SELECT * FROM "user" WHERE json_extract_path_text("attributes"::json,'name') = 'jinzhu'

自定义数据类型集合

我们创建了一个 Github 仓库,用于收集各种自定义数据类型https://github.com/go-gorm/datatypes,非常欢迎同学们的 pull request ;)