我知道使用自定义类型是一个常见的问题,但请原谅我...

我想定义一个自定义类型‘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
}

我怎么能这样做呢?

My progress thus far:

我定义了一个ConnectionInfo接口(我现在知道这在GORM中是无效的,但是我如何避免它呢?)

type ConnectionInfo interface {
    IsConnectionInfoType() bool
}

然后,我用两种类型实现了这个接口(并实现了scanner和valuer接口),如下所示:

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

WORKING ANSWER

多亏了Shahriar Ahmed,我有了一个惯用的解决方案,我只是想额外增加一点关于我是如何(try )来确保在Models包外部处理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,然后将其传递给我的Models包,该包将使用下面的代码获取具体类型并实例化一个‘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))
    }
}

推荐答案

您可以这样做,而不是使用这个联合.您可以克隆这些repo并运行代码.这起作用了.

package storage

import (
    "database/sql/driver"
    "encoding/json"
    "fmt"
    "log"

    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

type DataSourceType string

const (
    POSTGRES DataSourceType = "POSTGRES"
    MYSQL    DataSourceType = "MYSQL"
)

type PostgresConnectionInfo struct {
    Host     string
    Port     int
    Username string
    Password string
    DBName   string
}

type MySQLConnectionInfo struct {
    Host     string
    Port     int
    Username string
    Password string
    DBName   string
}

type ConnectionInfo struct {
    Postgres *PostgresConnectionInfo `gorm:"-" json:"postgres,omitempty"`
    Mysql    *MySQLConnectionInfo    `gorm:"-" json:"mysql,omitempty"`
}
type DataSource struct {
    gorm.Model
    Name           string
    Type           DataSourceType `sql:"type:ENUM('POSTGRES')" gorm:"column:data_source_type"`
    ConnectionInfo ConnectionInfo `gorm:"type:json" `
}

func (a *ConnectionInfo) Scan(src any) error {
    switch src := src.(type) {
    case nil:
        return nil
    case []byte:
        var res ConnectionInfo
        err := json.Unmarshal(src, &res)
        *a = res
        return err

    default:
        return fmt.Errorf("scan: unable to scan type %T into struct", src)
    }

}

func (a ConnectionInfo) Value() (driver.Value, error) {
    ba, err := json.Marshal(a)
    return ba, err
}

func GormTest2() {
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
    if err != nil {
        log.Fatal("could not open database")
    }
    err = db.AutoMigrate(&DataSource{})
    if err != nil {
        log.Fatal("could not migrate database")
    }
    createTestData1(db)
    fetchData1(db)
}

func createTestData1(db *gorm.DB) {
    ds := []DataSource{
        {
            Name: "Postgres",
            Type: POSTGRES,
            ConnectionInfo: ConnectionInfo{
                Postgres: &PostgresConnectionInfo{
                    Host:     "localhost",
                    Port:     333,
                    Username: "sdlfj",
                    Password: "sdfs",
                    DBName:   "sdfsd",
                },
            },
        },
        {
            Name: "Mysql",
            Type: MYSQL,
            ConnectionInfo: ConnectionInfo{
                Mysql: &MySQLConnectionInfo{
                    Host:     "localhost",
                    Port:     333,
                    Username: "sdlfj",
                    Password: "sdfs",
                    DBName:   "sdfsd",
                },
            },
        },
    }
    err := db.Create(&ds).Error
    if err != nil {
        log.Println("failed to create data")
    }
}

func fetchData1(db *gorm.DB) {
    var dsList []DataSource
    if err := db.Find(&dsList).Error; err != nil {
        log.Println("failed to load data")
    }
    log.Println(dsList)
}

Go相关问答推荐

如何从google.golang.org/grpc/stats包中将golang中不同事件的输出进行组合,以获取func HandlePRC

如何在另一个文件夹中使用Delve运行二进制文件?

GetSecretValue,get identity:get credentials:无法刷新缓存的凭据

Go 是否提供了标准或事实上的方法来处理单个语句中的错误(即内联错误处理)?

Golang Gorm Fiber - 如何将定义为别名的名称发送到索引模板?

命令行参数在 Golang 程序中不正确地接受为参数

对 CSV 进行单元测试失败

GoLang:net.LookupHost 返回重复的 ips

golang中如何声明多个接口约束?

emersion/go-imap - imap.FetchRFC822:无效内存地址或零指针取消引用

获取切片元素的地址是否意味着 Go 中元素的副本?

如何在 helm 中将字符串连接到 .AsConfig 的结果?

使用 AppID 在 Windows 中启动应用程序并获取 pid

没有堆栈跟踪的 go 程序崩溃是什么意思?

使用正则表达式拆分具有相同标题的数据块

如何在 Windows 中使用 github.com/AllenDang/giu 和 github.com/gordonklaus/portaudio 构建 GO 程序

Go 使用 struct 作为接口而不实现所有方法

如何从字符串中删除多个换行符`\n`但只保留一个?

无法识别同步错误.使用一次

如何解决在mac m1中运行gcc失败退出状态1?