🐘
sqldef を Go のアプリケーションに組み込む
これは Go Advent Calendar 2022 の 18 日目の記事です。
はじめに
sqldef は SQL で羃等に DB スキーマ管理ができるツールです。入力された DDL ファイルと実際の DB のスキーマを比較して必要な DDL を自動で生成・実行してくれます。
sqldef は CLI ツールとして提供されており、通常は DB に接続可能なサーバーにバイナリを配置して実行します。 sqldef をラップした npm ライブラリも提供されており、 Node.js のアプリケーションに組み込んで使うこともできます。また WebAssembly ライブラリも用意されています。
- k0kubun/sqldef: Idempotent schema management for MySQL, PostgreSQL, and more
- sqldef/node-sqldef: Simple wrapper around sqldef for easy use with node
- sqldef/sqldef.github.io: wasm support of sqldef
sqldef を Go のアプリケーションに組み込む
sqldef は Go で書かれています。また、一般的な Go ツールと同じように main パッケージと実装のパッケージが分離されています。そのため、公式の README などに記述はありませんが、ライブラリとして Go のアプリケーションに組み込むことができます。
実際に PostgreSQL で動作するコードを記載します。
import (
"errors"
"fmt"
"log"
"net/url"
"os"
"strconv"
"github.com/k0kubun/sqldef"
"github.com/k0kubun/sqldef/database"
"github.com/k0kubun/sqldef/database/postgres"
"github.com/k0kubun/sqldef/parser"
"github.com/k0kubun/sqldef/schema"
)
func migrate(databaseURL, schemaFile string) error {
u, err := url.Parse(databaseURL)
if err != nil {
return fmt.Errorf("failed to parse the database url: %w", err)
}
password, ok := u.User.Password()
if !ok {
return fmt.Errorf("failed to get password from the database url: %s", u.User.String())
}
port, err := strconv.Atoi(u.Port())
if err != nil {
if errors.Is(err, strconv.ErrSyntax) {
port = 5432
} else {
return fmt.Errorf("failed to convert port in the database url: %w", err)
}
}
db, err := postgres.NewDatabase(database.Config{
DbName: u.Path[1:],
User: u.User.Username(),
Password: password,
Host: u.Hostname(),
Port: port,
})
if err != nil {
return fmt.Errorf("failed to create a database adapter: %w", err)
}
sqlParser := database.NewParser(parser.ParserModePostgres)
desiredDDLs, err := sqldef.ReadFile(schemaFile)
if err != nil {
return fmt.Errorf("Failed to read %s: %w", schemaFile, err)
}
options := &sqldef.Options{DesiredDDLs: desiredDDLs}
if u.Hostname() == "localhost" {
os.Setenv("PGSSLMODE", "disable")
}
sqldef.Run(schema.GeneratorModePostgres, db, sqlParser, options)
}
基本的には sqldef の PostgreSQL 向け CLI である psqldef の main パッケージと同じ形です。アプリケーション起動時にこの関数を呼び出すことでマイグレーションを行うことができます。
おわりに
小規模なシステムではありますが、このコードは実際の本番環境で運用しています。 sqldef 自体はまだメジャーバージョン 0 なのでまれに破壊的は変更が入ることがあり、その変更には追従する必要がありますが、それ以外では特に大きな問題もなく運用できています。
Discussion
大変有用な記事をありがとうございます!
こちらの記事のコードを参考に実装して Heroku で実行してみたところ、次のようなエラーが起きました。
どうやら CREATE EXTENSION された PostgreSQL に対して実行する場合には、 psqldef の
--skip-extension
オプションに該当するSkipExtension: true
をdatabase.Config
で指定する必要があるようです。(もしかしたらスキーマ定義ファイルに
CREATE EXTENSION
自体も記述していれば回避できるのかもしれません。ただそこまでは試していません 🙄)コメントありがとうございます。
記事執筆当時はなかったオプションのようです。他に
--skip-view
というのも追加されていました。VIEW や EXTENSION を使わないユースケースでは今の記述で問題ないと思いますので、一旦記事本文はこのままにできればと思います 🙇
見てみたら既に
CREATE EXTENSION
のサポートが入っていました。 sqldef どんどん進化していてすごいですね。あっ、もし訂正求めているように受け取られたならすみません。こういうケースがあったよ、ぐらいの気持ちでコメントしました。
はい、それで全然問題ありません 🙋
おおっ、そうなんですね。では CREATE EXTENSION を書いても回避できそうですね。調べていただいてありがとうございます!