Сохранение перечисленных значений в базе данных

#sql #types #enums #go

#sql #типы #перечисления #Вперед

Вопрос:

Я новичок в Go, и я пытаюсь написать небольшую программу для сохранения перечисленных значений в базе данных. Я объявляю свои значения следующим образом:

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

Я использую эти значения в своей структуре следующим образом:

 type File struct {
    Name     string
    Type     FileType
    Size     int64
}
  

Я использую gorp для работы с базой данных, но, полагаю, использование gorp не имеет отношения к моей проблеме. Я помещаю данные в свою базу данных следующим образом:

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

но когда я пытаюсь извлечь материал…

 dbmap.Select(amp;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 reflection и sql, и там есть методы для такого преобразования, но они, похоже, терпят неудачу. Это ошибка? В чем проблема?
  2. Я понял, что можно реализовать sql.Scanner интерфейс, реализовав следующий метод:

     Scan(src interface{}) error
      

    Я попытался реализовать этот метод, и мне даже удалось получить правильное значение из src и преобразовать его в a FileType , но я был смущен, если я должен реализовать метод для « (f *FileType) или (f FileType) . В любом случае метод вызывается, однако я не могу перезаписать f (или, по крайней мере, обновление будет потеряно позже), и File экземпляры, считываемые из базы данных, всегда имели значение «0» в качестве значения File.Type .

У вас есть какие-либо идеи по этим двум пунктам?

Комментарии:

1. Вам действительно не следует использовать значение iota за пределами Go, например, в базе данных. Если вы когда-либо измените порядок констант или добавите новую в середине, значения iota изменятся, что приведет к несоответствию существующих записей в вашей базе данных.

Ответ №1:

Недавно у меня была такая же потребность, и решение заключается в реализации двух интерфейсов:

  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 }
  

Комментарии:

1. Это правильный ответ, и его следует принять. Отличная работа.

2. Просто из любопытства, я новичок в go. Есть ли веская причина сделать это int64? Похоже, вам нужен только int16 или даже int8, поскольку у вас, вероятно, не будет миллиардов возможных значений.

3. @bigblind это то, что использовалось в исходном вопросе. Обычно я бы не стал слишком беспокоиться о размерах значений, пока это не станет проблемой или проблемное пространство не оправдает этого.

Ответ №2:

Немного не по теме, но может быть полезно другим, поскольку я продолжал пересматривать этот вопрос / ответ при решении аналогичной проблемы при работе с полями перечисления postgres в golang (которые возвращаются в виде байтов).

  // 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 нет enum s. Ваши константы имеют тип string . Если вы хотите, чтобы они были типа Status (вы делаете), тогда вам следует использовать Incomplete Status = "incomplete" , Complete Status = "complete" , и т.д. (кроме того, идентификаторы с заглавными буквами не являются идиоматическими Go).

2. @DaveC спасибо, обновлено! Просто для ясности, я имею в виду фактический enum тип столбца postgres, но с хорошими исправлениями.

Ответ №3:

  1. Go должен быть специфичным для типов, что иногда может быть проблемой.
  2. (f FileType) дешевле, чем (f *FileType) для «собственных» типов, в значительной степени, если у вас нет сложного типа, почти всегда лучше не использовать указатель.
  3. Что вы имеете в виду, это не перезаписывает его? вы повторно сохранили структуру после ее изменения?

Комментарии:

1. что касается 2: хорошо, но как мне изменить «f» в случае «(f FileType)», когда я пишу что-то вроде «f = book» в методе сканирования, это не имеет никакого эффекта.

2. Вы можете изменить свое значение, только если оно работает с получателем указателя. Взгляните на реализацию sql.NullInt64 в stdlib .