gorm 自定义数据类型——迹忆客-ag捕鱼王app官网
gorm 提供了少量接口,使用户能够为 gorm 定义支持的数据类型,这里以 json 为例
实现自定义数据类型
scanner / valuer
自定义的数据类型必须实现 scanner
和 valuer
接口,以便让 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 标签 中读取字段的数据库类型,如果找不到,则会检查该结构体是否实现了 gormdbdatatypeinterface
或 gormdatatypeinterface
接口,然后使用接口返回值作为数据类型
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 没有实现 gormdbdatatypeinterface
或 gormdatatypeinterface
接口,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 仓库,用于收集各种自定义数据类型 。