gorm のマイグレーション機能の挙動

2019-02-19

スペック

  • go version go1.11.5
  • gorm v1.9.2
  • postgreSQL 11.1

ファイル構成

Webアプリを作って行く途中でいろいろ試したので無駄にファイルが分かれていますがお気になさらず...

main.go では models.InitDB() を呼び出してるだけです。

// main.go
package main

import (
	"github.com/ほにゃらら/models"
)

func main() {
	models.InitDB()
}

InitDB() では、データベースに接続して AutoMigrate() しています。ほんとは db.Close() ちゃんとしたほうがいいんでしょうけどとりあえずこれで。

// db.go
package models

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres"
	"os"
)

var db *gorm.DB

func InitDB() {
	connectDB()
	db.AutoMigrate(&Player{})
}

func connectDB() {
	var err error
	db, err = gorm.Open(
		"postgres",
		fmt.Sprintf(
			"host=%s user=%s dbname=%s password=%s sslmode=disable",
			os.Getenv("PG_HOST"),
			os.Getenv("PG_PORT"),
			os.Getenv("PG_USER"),
			os.Getenv("PG_DB"),
			os.Getenv("PG_PASSWORD"),
		),
	)
	if err != nil {
		panic(err)
	}
}

Player 構造体の定義をいろいろ変えてみて、テーブル定義がどうなるのかを見ていきます。

マイグレーションを実行してみよう

models.go 内の Player 構造体の定義をいくつか試します。

Goのデータ型との対応

まずは Golang の型が PostgreSQL のカラム定義ではどうなるのかを試してみます。

import (
	"time"
)

type Player struct {
	Name      string
	Age       int
	Birthday  time.Time
	Activated bool
	Struct    Address
	Pointer   *string
}
ColumnTypeCollationNullableDefault
nametext
ageinteger
birthdaytimestamp with time zone
activatedboolean
pointertext

配列型、スライス型、関数型については、invalid sql type と怒られました。構造体は怒られはしませんが、データベースには反映されていません。

また、インデックスやカラム制約も定義されていませんでした。

exportされていないフィールド

構造体に小文字から始まる (つまりexportされていない) フィールドがある場合を試してみます。

type Player struct {
	Name         string
	passwordHash string
	deleted      bool
}

結果はこんな感じ。

ColumnTypeCollationNullableDefault
nametext

exportされていないフィールドはデータベースに反映されないんですね。

gorm.Model をインポート

gormで用意されている基本的なフィールドを利用してみます。

type Player struct {
	gorm.Model
	Name string
}

この構造体は次のものと等価です。

type Player struct {
	ID        uint `gorm:"primary_key"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt *time.Time
	Name      string
}

この構造体に対するテーブル定義は次のようになりました。

ColumnTypeCollationNullableDefault
idintegernot nullnextval('players_id_seq'::regclass)
created_attimestamp with time zone
updated_attimestamp with time zone
deleted_attimestamp with time zone
nametext
Indexes:
"players_pkey" PRIMARY KEY, btree (id)
"idx_players_deleted_at" btree (deleted_at)

関連テーブル

次は関連テーブルを定義してみます。公式ドキュメント に書かれているとおりに構造体を定義しました。

type Player struct {
	Name      string
	Address   Address
	AddressID int
}

type Address struct {
	gorm.Model
	Prefecture string
	City       string
}

また、Address もマイグレート出来るように InitDB の中身を以下のように変更しておきます。

func InitDB() {
	connectDB()
	db.AutoMigrate(&Player{}, &Address{})
}

するとそれぞれのテーブル定義は次のようになりました。

players

ColumnTypeCollationNullableDefault
nametext
address_idinteger

addresses

ColumnTypeCollationNullableDefault
idintegernot nullnextval('addresses_id_seq'::regclass)
created_attimestamp with time zone
updated_attimestamp with time zone
deleted_attimestamp with time zone
prefecturetext
citytext
Indexes:
"addresses_pkey" PRIMARY KEY, btree (id)
"idx_addresses_deleted_at" btree (deleted_at)

外部キー制約は定義できないようです。

まとめ

構造体の定義をそのまま使えるのは便利ですね。しかし外部キー制約を指定することができなかったり、ロールバック機能が無かったりするので、しっかりしたアプリを構築する場合は別のマイグレーションツールを使ったほうが良いかもしれません。