How can i add enum in gorm?
Asked Answered
U

7

10

I am writing PostgreSQL table schema.

type TestTable struct {
    ID        int    `gorm:"column:id;primaryKey;autoIncrement"`
    CarType   string `gorm:"column:car_type"`
}

So how can i add car types like "SEDAN", "HATCHBACK", "MINIVAN" as enum data type

Umbrageous answered 3/8, 2021 at 13:48 Comment(1)
Reffer to this github issue answer as manual - github.com/go-gorm/gorm/issues/1978#issuecomment-476673540Jonna
N
25

Assuming you are using GORM with PostgreSQL. First in your database create a type.

CREATE TYPE car_type AS ENUM (
    'SEDAN',
    'HATCHBACK',
    'MINIVAN');

Then you will need to define the following model:

import "database/sql/driver"

type carType string

const (
    SEDAN  carType = "SEDAN"
    HATCHBACK carType = "HATCHBACK"
    MINIVAN carType = "MINIVAN"
)

func (ct *carType) Scan(value interface{}) error {
    *ct = carType(value.([]byte))
    return nil
}

func (ct carType) Value() (driver.Value, error) {
    return string(ct), nil
}

type MyTable struct {
    gorm.Model
    CarType carType `gorm:"type:car_type"`
}

func (MyTable) TableName() string {
    return "my_table"
}

Note for MySQL users, you can add struct tag gorm: or sql: so you don't have to run raw query to create enum in the database.

CarType carType `gorm:"type:enum('SEDAN', 'HATCHBACK', 'MINIVAN')";"column:car_type"`

OR

CarType carType `sql:"type:ENUM('SEDAN', 'HATCHBACK', 'MINIVAN')" gorm:"column:car_type"`
Neither answered 3/8, 2021 at 14:9 Comment(10)
Thanks, Nick for the response, but is there no support for Enum datatype? how we do for other data types like numeric etc. In this, I need to write the raw query to create an enum type in DB first.Umbrageous
You are right. There is no datatype given in the language as enum but we can represent it. Follow the first 3 responses to the following question, it will help you understanding enums in golang . #14426866 On the other part, I think you can try without creating enum in the database. But still without it being enum in the database, you will be risking parsing non-constant values in the database to the constants in your program. I think it's unsafe.Neither
I followed the same steps and also referred it from the main github issue on which this answer is based, But for me the line => CarType carType sql:"car_type" is just creating a text(string) column in postgres DB, I already have created the enum in my DB. I tried everything, no error is thrown, don't know whats the problem. Anyone having the same issue or resolution please reply.Jasmin
@Jasmin We made it work by using gorm:"type:car_type"Dews
How would you make this (enum) work for a "Has Many" or "Many to Many" association?Loads
@RomanDroppa mind sharing your solution as answer? looking for similar..thanksCellular
Ideally you should not need to run a custom sql query when using an ORM. This answer technically works, but there is a way to do this without the need to run a custom sql migration/query. My answer gives an example of how to do this without an additional query.Voluptuary
@CodyPotter The question is about PostgreSQL and your solution will not work with PostgreSQL. Your code will create a text column in postgres. As this question's title doesn't mention postgres, will put up a note for mysql.Neither
Ah good catch. I didn’t realize the question was specific to Postgres. I have to imagine there’s a way to do this in Postgres without an additional query though. If not I’d consider it a gorm bug.Voluptuary
I think this is using Serializer to have Enum like functionalityBootle
D
2

in order to extend Nick's answer I am adding the following for automation:

Assuming you have DBClient struct, you can create a method that creates this car type:

func (psqlClient *DBClient) CreateCarTypeEnum() error {
    result := psqlClient.db.Exec("SELECT 1 FROM pg_type WHERE typname = 'car_type';")

    switch {
    case result.RowsAffected == 0:
        if err := psqlClient.db.Exec("CREATE TYPE car_type AS ENUM ('SEDAN', 'HATCHBACK', 'MINIVAN');").Error; err != nil {
            log.Error().Err(err).Msg("Error creating car_type ENUM")
            return err
        }

        return nil
    case result.Error != nil:
        return result.Error

    default:
        return nil
    }
}
Debauched answered 3/12, 2021 at 17:4 Comment(0)
V
2

Edit: someone pointed out this only works with MySQL. I would take my answer down but someone using MySQL may find it helpful.

Here's an answer that doesn't require you to create a SQL type beforehand. My source for this solution was this github issue

For your field tag, use this:

type TestTable struct {
    ID        int     `gorm:"column:id;primaryKey;autoIncrement"`
    CarType   carType `sql:"type:ENUM('SEDAN', 'HATCHBACK', 'MINIVAN')" gorm:"column:car_type"`
}

You'll also need to add the Scan and Value methods attached to your carType type.

type carType string

const (
    SEDAN carType = "SEDAN"
    HATCHBACK carType = "HATCHBACK"
    MINIVAN carType = "MINIVAN"
)

func (self *carType) Scan(value interface{}) error {
    *self = carType(value.([]byte))
    return nil
}

func (self carType) Value() (driver.Value, error) {
    return string(self), nil
}
Voluptuary answered 2/9, 2022 at 23:34 Comment(0)
C
1

on a side note- if you decide to go with slightly different approach: you can define your enums as int, and leverage iota. then you can use code generator to create sql Scaner/Valuer but also json/text representations. for example: https://github.com/dmarkham/enumer

Cyrillus answered 4/8, 2021 at 8:29 Comment(0)
N
1

Just like you write in sql, you can use type and add whatever datatype you want like you write in sql.

type MyTable struct {
    CarType    string `gorm:"column:car_type;type:enum('SEDAN','HATCHBACK','MINIVAN')" json:"car_type"`
}
Neel answered 10/10, 2022 at 3:53 Comment(2)
This answer doesn't make use of gorm enumerations. I also tried this exact syntax and a sql field tag is required.Voluptuary
No, it doesn't require sql field tag. Which databae type do you use? I use in mysql. It works fine. no sql field tag is required.Neel
E
0

An update, it will not work with sql:"car_type" instead use gorm:"car_type". That too once you have manually created the custom enum type into the database.

Exhaustive answered 21/6, 2022 at 13:21 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Monkfish
J
-1

https://gorm.io/docs/data_types.html => impl Scan & Value methods.


If enum is generated by protoc, can use this plugin

https://github.com/yangyang5214/protoc-gen-gorm-serializer .

# task.proto
syntax = "proto3";

enum TaskStatus {
  Unknown = 0;
  Running = 1;
  Exiting = 2;
  Pending = 3;
}


# gen gorm-serializer.pb.go
protoc --proto_path=.  --go_out=paths=source_relative:. --gorm-serializer_out=paths=source_relative:.  task.proto

# use 
type Task struct {
    gorm.Model
    Name   string
    Status example.TaskStatus `gorm:"type:int"`
}
Jaggery answered 2/8, 2023 at 14:58 Comment(1)
If that doesn't work, by directly mapping fields to enums that are generated by proto. can use this plugin to gen Scan & Value method.Jaggery

© 2022 - 2024 — McMap. All rights reserved.