教程 > gorm 教程 > 阅读:181

gorm 自定义数据类型——迹忆客-ag捕鱼王app官网

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


实现自定义数据类型

scanner / valuer

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

例如:

type json json.rawmessage
// 实现 sql.scanner 接口,scan 将 value 扫描至 jsonb
func (j *json) scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.new(fmt.sprint("failed to unmarshal jsonb value:", value))
  }
  result := json.rawmessage{}
  err := json.unmarshal(bytes, &result)
  *j = json(result)
  return err
}
// 实现 driver.valuer 接口,value 返回 json value
func (j json) value() (driver.value, error) {
  if len(j) == 0 {
    return nil, nil
  }
  return json.rawmessage(j).marshaljson()
}

有许多第三方包实现了 scanner/valuer 接口,可与 gorm 一起使用,例如:

import (
  "github.com/google/uuid"
  "github.com/lib/pq"
)
type post struct {
  id     uuid.uuid `gorm:"type:uuid;default:uuid_generate_v4()"`
  title  string
  tags   pq.stringarray `gorm:"type:text[]"`
}

gormdatatypeinterface

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

type gormdatatypeinterface interface {
  gormdatatype() string
}
type gormdbdatatypeinterface interface {
  gormdbdatatype(*gorm.db, *schema.field) string
}

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

func (json) gormdatatype() string {
  return "json"
}
type user struct {
  attrs json
}
func (user user) beforecreate(tx *gorm.db) {
  field := tx.statement.schema.lookupfield("attrs")
  if field.datatype == "json" {
    // 做点什么...
  }
}

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

func (json) gormdbdatatype(db *gorm.db, field *schema.field) string {
  // 使用 field.tag、field.tagsettings 获取字段的 tag
  // 查看 https://github.com/go-gorm/gorm/blob/master/schema/field.go 获取全部的选项
  // 根据不同的数据库驱动返回不同的数据类型
  switch db.dialector.name() {
  case "mysql", "sqlite":
    return "json"
  case "postgres":
    return "jsonb"
  }
  return ""
}

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

type nullstring struct {
  string string // 使用第一个字段的数据类型
  valid  bool
}
type user struct {
  name nullstring // 数据类型会是 string
}

gormvaluerinterface

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

// gorm valuer interface
type gormvaluerinterface interface {
  gormvalue(ctx context.context, db *gorm.db) clause.expr
}

使用 sql 表达式进行 create/update

type location struct {
    x, y int
}
func (loc location) gormdatatype() string {
  return "geometry"
}
func (loc location) gormvalue(ctx context.context, db *gorm.db) clause.expr {
  return clause.expr{
    sql:  "st_pointfromtext(?)",
    vars: []interface{}{fmt.sprintf("point(%d %d)", loc.x, loc.y)},
  }
}
// scan 方法实现了 sql.scanner 接口
func (loc *location) scan(v interface{}) error {
  // scan a value into struct from database driver
}
type user struct {
  id       int
  name     string
  location location
}
db.create(&user{
  name:     "jinzhu",
  location: location{x: 100, y: 100},
})
// insert into `users` (`name`,`point`) values ("jinzhu",st_pointfromtext("point(100 100)"))
db.model(&user{id: 1}).updates(user{
  name:  "jinzhu",
  location: location{x: 100, y: 100},
})
// update `user_with_points` set `name`="jinzhu",`location`=st_pointfromtext("point(100 100)") where `id` = 1

你也可以根据 sql 表达式进行 create/update,查看 create from sql expr 和 update with sql expression 获取详情

基于 context 的值

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

type encryptedstring struct {
  value string
}
func (es encryptedstring) gormvalue(ctx context.context, db *gorm.db) (expr clause.expr) {
  if encryptionkey, ok := ctx.value("tenantencryptionkey").(string); ok {
    return clause.expr{sql: "?", vars: []interface{}{encrypt(es.value, encryptionkey)}}
  } else {
    db.adderror(errors.new("invalid encryption key"))
  }
  return
}

条件表达式

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

type expression interface {
    build(builder builder)
}

查看 json 和 sql builder 获取详情,下面是一个示例:

// 根据 clause expression 生成 sql
db.find(&user, datatypes.jsonquery("attributes").haskey("role"))
db.find(&user, datatypes.jsonquery("attributes").haskey("orgs", "orga"))
// mysql
// select * from `users` where json_extract(`attributes`, '$.role') is not null
// select * from `users` where json_extract(`attributes`, '$.orgs.orga') is not null
// postgresql
// select * from "user" where "attributes"::jsonb ? 'role'
// select * from "user" where "attributes"::jsonb -> 'orgs' ? 'orga'
db.find(&user, datatypes.jsonquery("attributes").equals("jinzhu", "name"))
// mysql
// select * from `user` where json_extract(`attributes`, '$.name') = "jinzhu"
// postgresql
// select * from "user" where json_extract_path_text("attributes"::json,'name') = 'jinzhu'

自定义数据类型集合

https://github.com/go-gorm/datatypes 这是一个 github 仓库,用于收集各种自定义数据类型 。

查看笔记

扫码一下
查看教程更方便
网站地图