将枚举值保存到数据库

18

我是Go的新手,正在尝试编写一个小程序将枚举值保存到数据库中。我声明值的方式如下:

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

我在我的结构体中像这样使用这些值:

type File struct {
    Name     string
    Type     FileType
    Size     int64
}

我在数据库方面使用gorp,但我猜gorp的使用与我的问题无关。我像这样将东西放入我的数据库中:

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

但是当我尝试检索东西时...

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

我收到以下错误:

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

当我将int64用作const(...)的类型和File.Type字段的类型时,一切都正常工作,但我是Go的新手,想要理解这个问题。我认为我有两个问题:

  1. 为什么Go不能成功地转换这些东西?我查看了Go反射和SQL包的源代码,它们都有用于这种转换的方法,但它们似乎失败了。这是一个错误吗?问题出在哪里?
  2. 我发现,可以通过实现以下方法来实现sql.Scanner接口:

    Scan(src interface{}) error
    

    我尝试实现该方法,甚至能够从src中获取正确的值并将其转换为FileType,但我困惑于是否应该为“(f *FileType)”或者是“(f FileType)”实现该方法。无论哪种方式都能调用该方法,但我无法覆盖f(或者至少后续更新会丢失),从数据库读取的File实例总是将“0”作为File.Type的值。

您有任何关于这两个问题的想法吗?


你真的不应该在Go的范围之外使用iota值,例如在数据库中。如果您曾经重新排序常量或在中间添加新常量,则iota值将更改,导致数据库中现有记录不匹配。 - Ferdy Pruis
3个回答

30

最近我也有同样的需求,解决方案是实现两个接口:

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

这是一个可行的示例:

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 }

2
这是正确的答案,应该被接受。干得好。 - Darrrrrren
1
只是出于好奇,我是一个完全的Go新手。有没有很好的理由将其设置为int64?似乎你只需要int16甚至int8,因为你可能不会有数十亿个可能的值。 - bigblind
1
@bigblind 这是原始问题使用的。通常情况下,我不会过于担心价值大小,直到它成为一个问题,或者问题空间需要它。 - Alec Thomas

9

有点离题,但可能对其他人有用,因为我在解决类似问题时一直回顾这个问题/答案,当使用golang处理postgres枚举字段(以字节返回)时也很有用。

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

1
Go语言没有枚举类型。你的常量应该是字符串类型。如果你想让它们成为Status类型(你需要这样做),那么你应该使用Incomplete Status = "incomplete"Complete Status = "complete"等等(同时,所有大写字母的标识符在Go语言中不是惯用的)。 - Dave C
1
@DaveC 谢谢,已更新!只是为了明确起见,我指的是Postgres实际的enum列类型,但是你的更正很好。 - steevee

0
  1. Go需要具体指定类型,有时可能会很麻烦。
  2. (f FileType)对于“本地”类型而言比(f *FileType)更便宜,除非你有一个复杂的类型,否则几乎总是最好不要使用指针。
  3. 你说的不覆盖是什么意思?你修改后是否重新保存了结构体?

关于2:好的,但是当我在Scan方法中写入"f = book"时,如何更改"(f FileType)"中的"f"呢?这种情况下没有效果。 - Wolf
只有在指针接收器上操作时,您才能修改自己的值。请查看 Stdlibsql.NullInt64 的实现。 - seong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接