LoginSignup
2
0

More than 1 year has passed since last update.

GolangにORM導入(go-pg migration編)

Last updated at Posted at 2022-02-18

はじめに

GolangのORMであるgo-pgの導入です。本記事ではgo-pgのチュートリアルに沿ってマイグレーションを実行します。
※go-pgは現在メンテナンスモードのため、bunへの移行が推奨されています。今から学ばれている方はbunを利用することをお勧めいたします。私は諸事情があり、あえてgo-pgで開発をしています。

環境情報

PC:Mac(CPUはintel製)
Go:1.17.6 ←Goのver大事。versionは1.16以降をインストールしてください。
開発エディタ:Visual Studio Code

ディレクトリ構成

ディレクトリ構成
~/go/src/go-pg-migrate $ tree
.
├── docker-compose.yml
├── examples
│   ├── 1_initial.go
│   ├── 2_add_id.go
│   ├── 3_seed_data.go
│   └── main.go
├── go.mod
└── go.sum

1 directory, 7 files

Hands-On スタート

$GOPATH/src配下にgo-pg-migrateディレクトリを作成。

ライブラリのインストール

ライブラリのインストール
~/go/src/go-pg-migrate $ go mod init
go: creating new go.mod: module go-pg-migrate

~/go/src/go-pg-migrate $ go get github.com/go-pg/migrations/v8
go: downloading github.com/go-pg/migrations/v8 v8.1.0
go: downloading github.com/go-pg/migrations v6.7.3+incompatible
go get: added github.com/go-pg/migrations/v8 v8.1.0
go get: added github.com/go-pg/pg/v10 v10.4.0
go get: added github.com/go-pg/zerochecker v0.2.0
go get: added github.com/golang/protobuf v1.4.3
go get: added github.com/jinzhu/inflection v1.0.0
go get: added github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc
go get: added github.com/vmihailenco/bufpool v0.1.11
go get: added github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1
go get: added github.com/vmihailenco/tagparser v0.1.2
go get: added go.opentelemetry.io/otel v0.13.0
go get: added golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
go get: added golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0
go get: added golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7
go get: added google.golang.org/appengine v1.6.7
go get: added google.golang.org/protobuf v1.25.0
go get: added mellium.im/sasl v0.2.1

DBの準備

docker-compose.ymlの作成
version: '3.1'

services:
  postgres:
    container_name: postgres
    image: postgres:13
    ports:
      - 5432:5432
    volumes:
      - ./.data/postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
DBスキーマ(pg_migrations_example)の作成
~/go/src/go-pg-migrate $ psql -U postgres -h localhost
Password for user postgres:                  ←Passwordはpostgresです。
psql (14.1, server 13.4 (Debian 13.4-1.pgdg100+1))
Type "help" for help.
postgres=# CREATE DATABASE pg_migrations_example;
CREATE DATABASE

main.goの作成

examples/main.goの作成
package main

import (
    "flag"
    "fmt"
    "os"

    "github.com/go-pg/migrations/v8"
    "github.com/go-pg/pg/v10"
)

const usageText = `This program runs command on the db. Supported commands are:
  - init - creates version info table in the database
  - up - runs all available migrations.
  - up [target] - runs available migrations up to the target one.
  - down - reverts last migration.
  - reset - reverts all migrations.
  - version - prints current db version.
  - set_version [version] - sets db version without running migrations.
Usage:
  go run *.go <command> [args]
`

func main() {
    flag.Usage = usage
    flag.Parse()

    db := pg.Connect(&pg.Options{
        User:     "postgres",
        Password:  "postgres", // Passwordは追加必要。公式サンプルには記載なし。
        Database: "pg_migrations_example",
    })

    oldVersion, newVersion, err := migrations.Run(db, flag.Args()...)
    if err != nil {
        exitf(err.Error())
    }
    if newVersion != oldVersion {
        fmt.Printf("migrated from version %d to %d\n", oldVersion, newVersion)
    } else {
        fmt.Printf("version is %d\n", oldVersion)
    }
}

func usage() {
    fmt.Print(usageText)
    flag.PrintDefaults()
    os.Exit(2)
}

func errorf(s string, args ...interface{}) {
    fmt.Fprintf(os.Stderr, s+"\n", args...)
}

func exitf(s string, args ...interface{}) {
    errorf(s, args...)
    os.Exit(1)
}

マイグレーションファイルの初期化

マイグレーションファイルの初期化
~/go/src/go-pg-migrate/examples $ go run main.go init
version is 0

→go_migrationsが作成されたことを確認。
スクリーンショット 2022-02-18 15.01.05.png

ちなみに、initを実行せずmain.goを実行しようとする下記のようなエラーが出ます。

main.go実行
~/go/src/go-pg-migrate/examples $ go run main.go 
table "gopg_migrations" does not exist; did you run init?
exit status 1

TBL(my_table)作成

では、実際にTBLを作成しましょう。今回はmy_tableを作成します。

1_initial.goの作成
package main

import (
    "fmt"

    "github.com/go-pg/migrations/v8"
)

func init() {

    migrations.MustRegisterTx(func(db migrations.DB) error {
        fmt.Println("creating table my_table...")
        _, err := db.Exec(`CREATE TABLE my_table()`) //my_tableを作成する。
        return err
    }, func(db migrations.DB) error {
        fmt.Println("dropping table my_table...")
        _, err := db.Exec(`DROP TABLE my_table`)
        return err
    })
}

migration実行
~/go/src/go-pg-migrate/examples $ go run *.go                                                    
creating table my_table...
migrated from version 0 to 1

→my_tableが作成されていることを確認。
スクリーンショット 2022-02-18 15.08.24.png

TBLのカラムを修正する。

2_add_id.goの作成
package main

import (
    "fmt"

    "github.com/go-pg/migrations/v8"
)

func init() {
    migrations.MustRegisterTx(func(db migrations.DB) error {
        fmt.Println("adding id column...")
        _, err := db.Exec(`ALTER TABLE my_table ADD id serial`)
        return err
    }, func(db migrations.DB) error {
        fmt.Println("dropping id column...")
        _, err := db.Exec(`ALTER TABLE my_table DROP id`)
        return err
    })
}
migration実行
~/go/src/go-pg-migrate/examples $ go run *.go 
adding id column...
migrated from version 1 to 2

スクリーンショット 2022-02-18 15.12.44.png
→my_tableにidカラムが追加されていることを確認。

レコード追加

3_seed_data.goの作成
package main

import (
    "fmt"

    "github.com/go-pg/migrations/v8"
)

func init() {
    migrations.MustRegisterTx(func(db migrations.DB) error {
        fmt.Println("seeding my_table...")
        _, err := db.Exec(`INSERT INTO my_table VALUES (1)`)
        return err
    }, func(db migrations.DB) error {
        fmt.Println("truncating my_table...")
        _, err := db.Exec(`TRUNCATE my_table`)
        return err
    })
}
migration実行
~/go/src/go-pg-migrate/examples $ go run *.go 
seeding my_table...
migrated from version 2 to 3

→レコードに値1が格納されていることを確認。
スクリーンショット 2022-02-18 15.16.11.png

余談

maigration初期化時に作成したgopg_migrationsの状態は下記の通りです。version管理をしっかりやってくれているのですね😁
スクリーンショット 2022-02-18 15.17.37.png

追記

他のサンプルでも実行。

ディレクトリ構成
~/go/src/go-pg $ tree
.
├── docker-compose.yml
├── go.mod
├── go.sum
└── main.go

0 directories, 4 files
main.go
package main

import (
    "fmt"

    "github.com/go-pg/pg/v10"
    "github.com/go-pg/pg/v10/orm"
)

type User struct {
    Id     int64
    Name   string
    Emails []string
}

func (u User) String() string {
    return fmt.Sprintf("User<%d %s %v>", u.Id, u.Name, u.Emails)
}

type Story struct {
    Id       int64
    Title    string
    AuthorId int64
    Author   *User
}

func (s Story) String() string {
    return fmt.Sprintf("Story<%d %s %s>", s.Id, s.Title, s.Author)
}

func ExampleDB_Model() {
    db := pg.Connect(&pg.Options{
        User: "postgres",
        Password: "postgres",
        Database: "sample",
    })
    defer db.Close()

    err := createSchema(db)
    if err != nil {
        panic(err)
    }

    user1 := &User{
        Name:   "admin",
        Emails: []string{"admin1@admin", "admin2@admin"},
    }
    _, err = db.Model(user1).Insert()
    if err != nil {
        panic(err)
    }

    _, err = db.Model(&User{
        Name:   "root",
        Emails: []string{"root1@root", "root2@root"},
    }).Insert()
    if err != nil {
        panic(err)
    }

    story1 := &Story{
        Title:    "Cool story",
        AuthorId: user1.Id,
    }
    _, err = db.Model(story1).Insert()
    if err != nil {
        panic(err)
    }

    // Select user by primary key.
    user := &User{Id: user1.Id}
    err = db.Model(user).WherePK().Select()
    if err != nil {
        panic(err)
    }

    // Select all users.
    var users []User
    err = db.Model(&users).Select()
    if err != nil {
        panic(err)
    }

    // Select story and associated author in one query.
    story := new(Story)
    err = db.Model(story).
        Relation("Author").
        Where("story.id = ?", story1.Id).
        Select()
    if err != nil {
        panic(err)
    }

    fmt.Println(user)
    fmt.Println(users)
    fmt.Println(story)
    // Output: User<1 admin [admin1@admin admin2@admin]>
    // [User<1 admin [admin1@admin admin2@admin]> User<2 root [root1@root root2@root]>]
    // Story<1 Cool story User<1 admin [admin1@admin admin2@admin]>>
}

// createSchema creates database schema for User and Story models.
func createSchema(db *pg.DB) error {
    models := []interface{}{
        (*User)(nil),
        (*Story)(nil),
    }

    for _, model := range models {
        err := db.Model(model).CreateTable(&orm.CreateTableOptions{
            Temp: false, //一時作成の場合はTrue
        })
        if err != nil {
            return err
        }
    }
    return nil
}

func main(){
    ExampleDB_Model()
}

usersテーブルとstoriesテーブルを追加。
スクリーンショット 2022-02-18 19.33.39.png
スクリーンショット 2022-02-18 19.33.52.png

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0