mysqlgogo-gormuinttinyint

How to prevent gorm from converting custom integer types into strings?


I have troubles while using gorm with custom types that have integer properties. Gorm tends to convert the integer values into strings for me. There are two separate problems:

Problem 1:

I have a custom type defined as follows:

type ClientType uint8

const (
    USER ClientType = iota
    SERVER
)

func (t ClientType) String() string {
    return [...]string{"User", "Service"}[t]

And then I have a ORM struct for Client defined like this:

type Client struct {
    ClientID uint64 `gorm:"primaryKey"`
    UserID uint64
    ClientType ClientType
    CreatedAt time.Time
}

When calling db.Create(&client), gorm automatically invokes the String() method on ClientType, and causes a data type mismatch in MySQL where I intend to store ClientType in a TINYINT column called client_type.

Problem 2:

So I thought if I can't figure out how to override the automatic calling of the String() method, I simply renamed the method to ToString() and call it when I need it. Now gorm can't invoke it any more, but instead, it casts the integer values into numeric strings instead. So USER which is 0, will now become '0' in the resulting SQL statement, and SERVER will become '1', although MySQL is able to cast the strings back to integers.

I still wonder what I did wrong to make gorm think I wanted these conversions.

  1. Is there a way to override the calling of String(), so I can still name the method conventionally?
  2. Is it the use of uint8 that made gorm converts the int to strings, since the other numeric values (ClientID and UserID) using uint64 were not affected by the issue? Is it some sort of caching of the DB schema that made gorm remember the old schema where client_type used to be an ENUM('User', 'Service') column?

Solution

  • As per gorm for custom types Value and Scan need to be implemented.

    You can try specifying type in struct (replace <TYPE> with column type) too.

    type Client struct {
        ClientID   uint64 `gorm:"primaryKey"`
        UserID     uint8
        ClientType ClientType `gorm:"type:<TYPE>"`
        CreatedAt  time.Time
    }
    

    String() does not seems to be issue as it is not called during creates, but only during finds.

    With db.LogMode(true), it is called during create also, but that seems to be for sql query logging and not for sending to DB.

    With Log Mode off

        db.LogMode(false)
        fmt.Println("Creating")
        db.Create(&Client{ClientID: 9, UserID: 8, ClientType: USER, CreatedAt: time.Now()})
        fmt.Println("Created")
    
        fmt.Println("Finding")
        db.First(&client)
        fmt.Printf("Found %v\n", client)
    

    output

    Creating
    Created
    Finding
    String() called with 0
    Found {9 8 User 2021-01-28 11:41:51.355633057 +0530 +0530}
    
    

    With Log Mode on

        db.LogMode(true)
        fmt.Println("Creating")
        db.Create(&Client{ClientID: 9, UserID: 8, ClientType: USER, CreatedAt: time.Now()})
        fmt.Println("Created")
    
        fmt.Println("Finding")
        db.First(&client)
        fmt.Printf("Found %v\n", client)
    

    Output:

    Creating
    String() called with 0
    [2021-01-28 11:39:30]  [0.95ms]  INSERT INTO "clients" ("client_id","user_id","client_type","created_at") VALUES (9,8,'User','2021-01-28 11:39:30')  
    [1 rows affected or returned ] 
    Created
    Finding
    [2021-01-28 11:39:30]  [1.49ms]  SELECT * FROM "clients"   LIMIT 1  
    [1 rows affected or returned ] 
    String() called with 0
    Found {9 8 User 2021-01-28 11:42:31.245628274 +0530 +0530}