#はじめに
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の準備
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
~/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の作成
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
ちなみに、initを実行せず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を作成します。
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
})
}
~/go/src/go-pg-migrate/examples $ go run *.go
creating table my_table...
migrated from version 0 to 1
##TBLのカラムを修正する。
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
})
}
~/go/src/go-pg-migrate/examples $ go run *.go
adding id column...
migrated from version 1 to 2
##レコード追加
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
})
}
~/go/src/go-pg-migrate/examples $ go run *.go
seeding my_table...
migrated from version 2 to 3
##余談
maigration初期化時に作成したgopg_migrationsの状態は下記の通りです。version管理をしっかりやってくれているのですね😁
#追記
他のサンプルでも実行。
~/go/src/go-pg $ tree
.
├── docker-compose.yml
├── go.mod
├── go.sum
└── main.go
0 directories, 4 files
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()
}
#参考
https://github.com/go-pg/migrations/tree/v6.7.3/example