GORMで使用できる独自のデータ型をユーザ自身で定義できるようにするために、GORMにはいくつかのインターフェースが用意されています。json を例として参照してみるとよいでしょう。
独自のデータ型を実装する Scanner / Valuer 独自のデータ型を使用するためには、Scanner と Valuer インターフェイスを実装する必要があります。これらのインターフェイスを実装することで、DBからの値の取得処理やDBへの保存処理をGORMが行うことが可能になります。
例:
type JSON json.RawMessagefunc (j *JSON) Scan(value interface {}) error { bytes, ok := value.([]byte ) if !ok { return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:" , value)) } result := json.RawMessage{} err := json.Unmarshal(bytes, &result) *j = JSON(result) return err } func (j JSON) Value() (driver.Value, error ) { if len (j) == 0 { return nil , nil } return json.RawMessage(j).MarshalJSON() }
多くのサードパーティ製パッケージが Scanner
/Valuer
インターフェイスを実装しており、それらはGORMと併用することができます。例:
import ( "github.com/google/uuid" "github.com/lib/pq" ) type Post struct { ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"` Title string Tags pq.StringArray `gorm:"type:text[]"` }
GormDataTypeInterface GORMはカラムのデータ型を type
tag から読み取ります。タグが指定されていない場合は、GormDBDataTypeInterface
か GormDataTypeInterface
が実装されているかをチェックします。インターフェイスが実装されている場合は、インターフェイスのメソッドの結果をデータ型として使用します。
type GormDataTypeInterface interface { GormDataType() string } type GormDBDataTypeInterface interface { GormDBDataType(*gorm.DB, *schema.Field) string }
GormDataType
メソッドの返却値は通常のデータ型として使用され、schema.Field
の DataType
からも取得できます。これは プラグインを作成する 際や hooksを利用する 際に役立ちます。例:
func (JSON) GormDataType() string { return "json" } type User struct { Attrs JSON } func (user User) BeforeCreate(tx *gorm.DB) { field := tx.Statement.Schema.LookUpField("Attrs" ) if field.DataType == "json" { } }
GormDBDataType
は通常、マイグレーション時に使用しているドライバに適切なデータ型を返します。例:
func (JSON) GormDBDataType(db *gorm.DB, field *schema.Field) string { switch db.Dialector.Name() { case "mysql" , "sqlite" : return "JSON" case "postgres" : return "JSONB" } return "" }
構造体が GormDBDataTypeInterface
や GormDataTypeInterface
インターフェイスを実装していない場合、GORMはその構造体の一番最初のフィールドからデータ型を推測します。例えば以下の NullString
では string
がデータ型として使用されます。
type NullString struct { String string Valid bool } type User struct { Name NullString }
GormValuerInterface GORMは GormValuerInterface
インターフェイスを提供しています。これにより、SQL式での作成/更新やコンテキストに基づいた値での作成/更新を行うことができます。例:
type GormValuerInterface interface { GormValue(ctx context.Context, db *gorm.DB) clause.Expr }
SQL Expr での作成/更新 type Location struct { X, Y int } func (loc Location) GormDataType() string { return "geometry" } func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr { return clause.Expr{ SQL: "ST_PointFromText(?)" , Vars: []interface {}{fmt.Sprintf("POINT(%d %d)" , loc.X, loc.Y)}, } } func (loc *Location) Scan(v interface {}) error { } type User struct { ID int Name string Location Location } db.Create(&User{ Name: "jinzhu" , Location: Location{X: 100 , Y: 100 }, }) db.Model(&User{ID: 1 }).Updates(User{ Name: "jinzhu" , Location: Location{X: 100 , Y: 100 }, })
マップを使用した SQL Expr でレコードを作成/更新することもできます。詳細については、 SQL式/Context Valuer で作成する および SQL式で更新する をチェックしてください。
コンテキストに基づく値 現在のコンテキストに基づいて値を作成または更新したい場合は、 GormValuerInterface
インターフェイスを実装することで実現可能です。
type EncryptedString struct { Value string } func (es EncryptedString) GormValue(ctx context.Context, db *gorm.DB) (expr clause.Expr) { if encryptionKey, ok := ctx.Value("TenantEncryptionKey" ).(string ); ok { return clause.Expr{SQL: "?" , Vars: []interface {}{Encrypt(es.Value, encryptionKey)}} } else { db.AddError(errors.New("invalid encryption key" )) } return }
Clause Expression クエリヘルパーを構築したい場合、clause.Expression
インターフェイスを実装する構造体を作成することで実現することができます。
type Expression interface { Build(builder Builder) }
詳細については JSON と SQL Builder を確認してください。以下は使い方の例です:
db.Find(&user, datatypes.JSONQuery("attributes" ).HasKey("role" )) db.Find(&user, datatypes.JSONQuery("attributes" ).HasKey("orgs" , "orga" )) db.Find(&user, datatypes.JSONQuery("attributes" ).Equals("jinzhu" , "name" ))
独自データ型のコレクション https://github.com/go-gorm/datatypes のGithubリポジトリに独自データ型のコレクションを用意しています。プルリクエストを歓迎しています!