Open2
SQLite + GORM + zap = ♥
いきなりサンプルコードから。
sample.go
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"sync"
"github.com/glebarez/sqlite"
"github.com/goark/errs"
"go.uber.org/zap"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"moul.io/zapgorm2"
)
type Planet struct {
gorm.Model
Name string
Mass float64
Distance float64
}
var planets = []Planet{
{Name: "Mercury", Mass: 0.055, Distance: .4},
{Name: "Venus", Mass: 0.815, Distance: .7},
{Name: "Earth", Mass: 1.0, Distance: 1.0},
{Name: "Mars", Mass: 0.107, Distance: 1.5},
}
const dbfile = "./sqlite.db"
func main() {
// initialize logger
zlogger := zap.NewExample()
defer zlogger.Sync()
glogger := zapgorm2.New(zlogger)
glogger.SetAsDefault()
// open SQLite file
db, err := gorm.Open(sqlite.Open(dbfile), &gorm.Config{Logger: glogger.LogMode(logger.Info)})
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
// migration
db.WithContext(context.TODO()).AutoMigrate(&Planet{})
// create data
var wg sync.WaitGroup
var errlist *errs.Errors
for _, p := range planets {
p := p
wg.Add(1)
go func() {
defer wg.Done()
if err := db.WithContext(context.TODO()).Transaction(func(tx *gorm.DB) error {
if t := tx.Create(&p); t.Error != nil {
fmt.Fprintln(os.Stderr, t.Error)
return tx.Error
}
return nil
}); err != nil {
errlist.Add(errs.Wrap(err, errs.WithContext("data", p)))
}
}()
}
wg.Wait()
err = errlist.ErrorOrNil()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
// read
data := []Planet{}
if tx := db.WithContext(context.TODO()).Order("mass asc, id asc").Find(&data); tx.Error != nil {
fmt.Fprintln(os.Stderr, tx.Error)
return
}
if err := json.NewEncoder(os.Stdout).Encode(data); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
// remove file
if err := os.Remove(dbfile); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
}
手順としては
- zap ロガーを生成。 GORM 用に decorate する
- SQLite ファイルをオープン(ファイルがない場合は生成)。ロガーとして 1 で生成したものをセットする
- Planet 型構造体を使ってテーブルを作成
- 作成したテーブルにデータを追加する。平行処理で別々の goroutine からアクセスしているのがポイント
- 格納したデータを mass 値でソートして取得する
- 取得したデータを JSON 形式にエンコードして出力
- 使い終わった SQLite ファイルを削除
といった感じ。
これを実行した結果が以下の通り。
$ go run sample.go | jq .
{
"level": "debug",
"msg": "trace",
"elapsed": "84.469µs",
"rows": -1,
"sql": "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=\"planets\""
}
{
"level": "debug",
"msg": "trace",
"elapsed": "8.046015ms",
"rows": 0,
"sql": "CREATE TABLE `planets` (`id` integer,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`mass` real,`distance` real,PRIMARY KEY (`id`))"
}
{
"level": "debug",
"msg": "trace",
"elapsed": "2.29142ms",
"rows": 0,
"sql": "CREATE INDEX `idx_planets_deleted_at` ON `planets`(`deleted_at`)"
}
{
"level": "debug",
"msg": "trace",
"elapsed": "537.511µs",
"rows": 1,
"sql": "INSERT INTO `planets` (`created_at`,`updated_at`,`deleted_at`,`name`,`mass`,`distance`) VALUES (\"2023-07-02 12:52:44.81\",\"2023-07-02 12:52:44.81\",NULL,\"Earth\",1.000000,1.000000) RETURNING `id`"
}
{
"level": "debug",
"msg": "trace",
"elapsed": "3.878395ms",
"rows": 1,
"sql": "INSERT INTO `planets` (`created_at`,`updated_at`,`deleted_at`,`name`,`mass`,`distance`) VALUES (\"2023-07-02 12:52:44.81\",\"2023-07-02 12:52:44.81\",NULL,\"Mars\",0.107000,1.500000) RETURNING `id`"
}
{
"level": "debug",
"msg": "trace",
"elapsed": "9.780918ms",
"rows": 1,
"sql": "INSERT INTO `planets` (`created_at`,`updated_at`,`deleted_at`,`name`,`mass`,`distance`) VALUES (\"2023-07-02 12:52:44.81\",\"2023-07-02 12:52:44.81\",NULL,\"Venus\",0.815000,0.700000) RETURNING `id`"
}
{
"level": "debug",
"msg": "trace",
"elapsed": "20.146606ms",
"rows": 1,
"sql": "INSERT INTO `planets` (`created_at`,`updated_at`,`deleted_at`,`name`,`mass`,`distance`) VALUES (\"2023-07-02 12:52:44.811\",\"2023-07-02 12:52:44.811\",NULL,\"Mercury\",0.055000,0.400000) RETURNING `id`"
}
{
"level": "debug",
"msg": "trace",
"elapsed": "313.95µs",
"rows": 4,
"sql": "SELECT * FROM `planets` WHERE `planets`.`deleted_at` IS NULL ORDER BY mass asc, id asc"
}
[
{
"ID": 4,
"CreatedAt": "2023-07-02T12:52:44.811046342+09:00",
"UpdatedAt": "2023-07-02T12:52:44.811046342+09:00",
"DeletedAt": null,
"Name": "Mercury",
"Mass": 0.055,
"Distance": 0.4
},
{
"ID": 2,
"CreatedAt": "2023-07-02T12:52:44.810572761+09:00",
"UpdatedAt": "2023-07-02T12:52:44.810572761+09:00",
"DeletedAt": null,
"Name": "Mars",
"Mass": 0.107,
"Distance": 1.5
},
{
"ID": 3,
"CreatedAt": "2023-07-02T12:52:44.810994925+09:00",
"UpdatedAt": "2023-07-02T12:52:44.810994925+09:00",
"DeletedAt": null,
"Name": "Venus",
"Mass": 0.815,
"Distance": 0.7
},
{
"ID": 1,
"CreatedAt": "2023-07-02T12:52:44.810782185+09:00",
"UpdatedAt": "2023-07-02T12:52:44.810782185+09:00",
"DeletedAt": null,
"Name": "Earth",
"Mass": 1,
"Distance": 1
}
]
ID 値を見ると分かるように,入力データの順番に ID が振られているわけではない。平行処理だから当たり前だけど。まぁ,でも,これで充分使えることが分かったのでよしとしよう。