【发布时间】:2022-11-25 06:54:03
【问题描述】:
我知道使用自定义类型是一个常见问题,但请耐心等待...
我想定义一个自定义类型“ConnectionInfo”(见下文):
type DataSource struct {
gorm.Model
Name string
Type DataSourceType `sql:"type:ENUM('POSTGRES')" gorm:"column:data_source_type"`
ConnectionInfo ConnectionInfo `gorm:"embedded"`
}
我想将 ConnectionInfo 限制为有限数量的类型之一,即:
type ConnectionInfo interface {
PostgresConnectionInfo | MySQLConnectionInfo
}
我怎样才能做到这一点?
到目前为止我的进展:
我定义了一个 ConnectionInfo 接口(我现在知道这在 GORM 中是无效的,但我该如何绕过它?)
type ConnectionInfo interface {
IsConnectionInfoType() bool
}
然后我用两种类型实现了这个接口(并实现了扫描器和估价器接口),如下所示:
type PostgresConnectionInfo struct {
Host string
Port int
Username string
Password string
DBName string
}
func (PostgresConnectionInfo) IsConnectionInfoType() bool {
return true
}
func (p *PostgresConnectionInfo) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return fmt.Errorf("failed to unmarshal the following to a PostgresConnectionInfo value: %v", value)
}
result := PostgresConnectionInfo{}
if err := json.Unmarshal(bytes, &result); err != nil {
return err
}
*p = result
return nil
}
func (p PostgresConnectionInfo) Value() (driver.Value, error) {
return json.Marshal(p)
}
但是我当然会收到以下错误:
unsupported data type: <myproject>/models.ConnectionInfo
工作答案
感谢 Shahriar Ahmed,我有一个惯用的解决方案,我只是想添加一些额外的信息来说明我是如何(尝试)在处理模型包之外的 ConnectionInfo 类型时确保一些类型安全的。
我的 ConnectionInfo 字段现在看起来像这样:
type DataSource struct {
gorm.Model
ConnectionInfo connectionInfo `gorm:"type:jsonb;not null"`
}
它的类型是 Shahriar 的建议,包括每个也实现 Scanner 和 Valuer 接口的子类型/变体:
type connectionInfo struct {
Postgres *PostgresConnectionInfo `gorm:"-" json:"postgres,omitempty"`
MySQL *MySQLConnectionInfo `gorm:"-" json:"mysql,omitempty"`
}
func (c *connectionInfo) Scan(src any) error {
switch src := src.(type) {
case nil:
return nil
case []byte:
var res connectionInfo
err := json.Unmarshal(src, &res)
*c = res
return err
default:
return fmt.Errorf("unable to scan type %T into connectionInfo", src)
}
}
func (c connectionInfo) Value() (driver.Value, error) {
return json.Marshal(c)
}
但是,我没有导出 'connectionInfo' 类型(通过使用小写的 'c'),并且我创建了一个导出的 'ConnectionInfo' 接口,'PostgresConnectionInfo' 和 'MySQLConnectionInfo' 类型实现了该接口:
type ConnectionInfo interface {
IsConnectionInfoType() bool
}
type PostgresConnectionInfo struct {
Host string `json:"host" binding:"required"`
Port int `json:"port" binding:"required"`
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
DBName string `json:"dbName" binding:"required"`
}
func (PostgresConnectionInfo) IsConnectionInfoType() bool {
return true
}
当想要引用抽象类型时,我将使用 ConnectionInfo,然后将其传递给我的模型包,该包将使用下面的方法获取具体类型并实例化“connectionInfo”类型:
func getConcreteConnectionInfo(connInfo ConnectionInfo) connectionInfo {
switch v := connInfo.(type) {
case *PostgresConnectionInfo:
return connectionInfo{Postgres: v}
case *MySQLConnectionInfo:
return connectionInfo{MySQL: v}
default:
panic(fmt.Sprintf("Unknown connection info type: %T", connInfo))
}
}
【问题讨论】:
-
是的,我知道这一点 - 我该如何解决这个问题?
-
恐怕没有像其他支持多态 ORM 的语言那样奇特的功能。在这里,我将实现 2 个字段(一次只填充一个)并使用
DataSource.Type的值来区分要查看的字段。或者我会使用额外的单个字符串字段,在其中序列化/反序列化连接信息到/从中,但我需要使用在DataSource上定义的AfterFind钩子,它会查看Type字段并根据其value 它会将 json 字符串反序列化为PostgresConnectionInfo或MySQLConnectionInfo。类似于通过`BeforeSave 进行序列化。 -
*忘了说字符串字段将包含 json。并且
ConnectionInfo字段需要被 gorm 使用gorm:"-"忽略。相当 hacky 的解决方案:/ -
我注意到 GORM 文档确实提到了对多态性的支持,但它没有提供太多关于如何使用它的信息gorm.io/docs/has_one.html#Polymorphism-Association
-
如果我的 ConnectionInfo 类型的结构不同,多态性是否适用? IE。连接到 postgres 和 influxdb 所需的详细信息将有所不同。