Home > database >  How to mimic a union type in Gorm?
How to mimic a union type in Gorm?

Time:11-27

I know that using custom types is a common question, but bear with me...

I would like to define a custom type 'ConnectionInfo' (see below):

type DataSource struct {
    gorm.Model

    Name           string
    Type           DataSourceType `sql:"type:ENUM('POSTGRES')" gorm:"column:data_source_type"`
    ConnectionInfo ConnectionInfo `gorm:"embedded"`
}

I would like to restrict ConnectionInfo to be one of a limited number of types, i.e.:

type ConnectionInfo interface {
    PostgresConnectionInfo | MySQLConnectionInfo
}

How can I do this?

My progress thus far:

I defined a ConnectionInfo interface (I now know this is invalid in GORM, but how do I get around it?)

type ConnectionInfo interface {
    IsConnectionInfoType() bool
}

I've then implemented this interface with two types (and implemented the scanner and valuer interfaces) like so:

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)
}

But of course I get the following error:

unsupported data type: /models.ConnectionInfo

CodePudding user response:

Instead of using this union, you can approach this way GITHUB LINK. You can clone these repo and run the code. This is working.

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)
}

  • Related