Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving enumerated values to a database

Tags:

types

sql

enums

go

I'm new to Go and I'm trying to write a little program to save enumerated values to a database. The way I declare my values is as follows:

type FileType int64
const (
    movie FileType = iota
    music
    book
    etc
)

I use these values in my struct like this:

type File struct {
    Name     string
    Type     FileType
    Size     int64
}

I use gorp for my database stuff, but I guess the use of gorp isn't relevant to my problem. I put stuff in my DB like this:

dbmap.Insert(&File{"MyBook.pdf",movie,1000})

but when I try to retrieve stuff…

dbmap.Select(&dbFiles, "select * from Files")

I get the following error:

panic: reflect.Set: value of type int64 is not assignable to type main.FileType

When I use int64 as the type for the const(...) and for the File.Type field, everything works fine, but I'm new to Go and want to understand the problem. The way I see it, I have two problems:

  1. Why can't Go convert this stuff successfully? I looked at the source code of the Go reflection and sql packages and there are methods for this kind of conversion, but they seem to fail. Is this a bug? What is the problem?
  2. I figured out, that one can implement the sql.Scanner interface by implementing the following method:

    Scan(src interface{}) error
    

    I tried to implement the method and I even was able to get the right value from src and convert it to a FileType, but I was confused if I should implement the method for "(f *FileType) or (f FileType). Either way the method gets invoked, however I'm not able to overwrite f (or at least the update gets lost later) and the File instances read from the DB always had a "0" as value for File.Type.

Do you have any ideas on those two points?

like image 743
Wolf Avatar asked Jun 17 '14 12:06

Wolf


People also ask

Can you store an enum in database?

By keeping the enum in your database, and adding a foreign key on the table that contains an enum value you ensure that no code ever enters incorrect values for that column. This helps your data integrity and is the most obvious reason IMO you should have tables for enums.

How are enum values stored?

A standard enum is usually implemented as an int32, the compiler will handle your enum as a synonym of int32 . Once a list of values is created for a enumeration those values are stored as literals against their display name(access name given at the time of declaration of enum).

What is enumeration in database?

Implementation Details. Enumerated (enum) types are data types that comprise a static, ordered set of values. They are equivalent to the enum types supported in a number of programming languages. An example of an enum type might be the days of the week, or a set of status values for a piece of data.

How is enum stored in SQL?

The ENUM data type is stored in two locations: the set of values is stored in the table metadata; in each row, only the set index is stored, as integer, which requires one byte for enums up to 255 entries large, then two for up to 65535 entries (see MySQL reference)


2 Answers

I recently had the same need, and the solution is to implement two interfaces:

  1. sql/driver.Valuer
  2. sql.Scanner

Here's a working example:

type FileType int64

func (u *FileType) Scan(value interface{}) error { *u = FileType(value.(int64)); return nil }
func (u FileType) Value() (driver.Value, error)  { return int64(u), nil }
like image 118
Alec Thomas Avatar answered Sep 23 '22 14:09

Alec Thomas


Slightly off-topic, but may be useful to others as I kept revisiting this question/answer when solving a similar problem when working with postgres enum fields in golang (which are returned as bytes).

 // Status values
 const ( 
     incomplete Status = "incomplete"
     complete   Status = "complete" 
     reject     Status = "reject"
 )

 type Status string

 func (s *Status) Scan(value interface{}) error {
     asBytes, ok := value.([]byte)
     if !ok {
         return errors.New("Scan source is not []byte")
     }
     *s = Status(string(asBytes))
     return nil
 }

 func (s SubjectStatus) Value() (driver.Value, error) {
     // validation would go here
     return string(s), nil
 }
like image 42
steevee Avatar answered Sep 22 '22 14:09

steevee