postgresqlgogo-gorm

unsupported data type: &[] error on GORM field where custom Valuer returns nil?


I was trying to create a GORM model for a postgres database, containing a type with a custom Scanner and Valuer that converts a string slice to and from a string, to be saved as a single database column. If the slice is either empty or nil, I want the database column to also be nil (as opposed to an empty string).

type MultiString []string

func (s *MultiString) Scan(src interface{}) error {
    str, ok := src.(string)
    if !ok {
        return errors.New("failed to scan multistring field - source is not a string")
    }
    *s = strings.Split(str, ",")
    return nil
}

func (s MultiString) Value() (driver.Value, error) {
    if s == nil || len(s) == 0 {
        return nil, nil
    }
    return strings.Join(s, ","), nil
}

The problem arises when I try and call AutoMigrate on the following struct:

type Person struct {
    ID      int
    Name    string
    Kids    *MultiString
}

I get the following errors multiple times:

[error] unsupported data type: &[]

Solution

  • In the Value method, replace the returned nil with sql.NullString{} - This is incorrect, as Value should not return another Valuer.

    The problem is that GORM is unsure of what the datatype should be for the newly defined type, so it tried to figure it out. Instead, the type should be explicitly defined, either with a tag in the model or by implementing a gorm method on the new type

    Tag in the model

    type MyModel struct {
        ...
        MyText MultiString `gorm:"type:text"`
    }
    

    This tells GORM to use the type text for the DB column type. With this strategy, the tag must be applied every time the new type is used in a model.

    GORM Methods

    Two GORM methods can be implemented on the new data type to tell GORM what database type should be used:

    Examples:

    func (MultiString) GormDataType() string {
      return "text"
    }
    
    func (MultiString) GormDBDataType(db *gorm.DB, field *schema.Field) string {
    
      // returns different database type based on driver name
      switch db.Dialector.Name() {
      case "mysql", "sqlite":
        return "text"
      }
      return ""
    }
    

    This one is useful for if the data type differs between what type of database you're using.