次世代DBマイグレーションツール「sqldef」

にあえん

October 6, 2023

先々月くらいにPrismaを知り、関連の記事をいろいろ書いていました。

ただ、最近Rustを触ることが多くてPrismaの使いどころがマイグレーションぐらいしかないのでちょっとしょんぼりしてたんですが、関連してちょっと気になるツールを見つけたので勉強がてら紹介します。

GitHub - k0kubun/sqldef: Idempotent schema management for MySQL, PostgreSQL, and more

このツールを使うと、Prismaでいうschema.prismaをSQLファイルで管理できるようになるとのこと!

すごい柔軟性!!!!!

つまり、1つのSQLファイルと現在のDBを比較して、追加・変更・削除されたテーブルや列などの差分情報を読み取り、マイグレーションを行ってくれるという感じです。

ちなみに、sqlx関連で調べて見ていた参考にしていたブログはこちら。

[Rust] sqlxを使ってみる #Rust - Qiita

インストール

まずは使用しているUbuntuにダウンロードしてみます。

私は普段使いがPostgresなので、以下のコマンドでpsqldefをインストールします。

curl -OL https://github.com/k0kubun/sqldef/releases/download/v0.16.9/psqldef_linux_amd64.tar.gz
sudo tar xf psqldef_linux_amd64.tar.gz -C /usr/local/bin/

インストールできたか確認してみます。

% psqldef --help
Usage:
  psqldef [option...] db_name

Application Options:
  -U, --user=username         PostgreSQL user name (default: postgres)
  -W, --password=password     PostgreSQL user password, overridden by $PGPASSWORD
  -h, --host=hostname         Host or socket directory to connect to the PostgreSQL server (default: 127.0.0.1)
  -p, --port=port             Port used for the connection (default: 5432)
      --password-prompt       Force PostgreSQL user password prompt
  -f, --file=filename         Read schema SQL from the file, rather than stdin (default: -)
      --dry-run               Don't run DDLs but just show them
      --export                Just dump the current schema to stdout
      --enable-drop-table     Enable destructive changes such as DROP (enable only table drops)
      --before-apply=         Execute the given string before applying the regular DDLs
      --config=               YAML file to specify: target_tables, skip_tables
      --help                  Show this help
      --version               Show this version

ヘルプコマンドが表示されたので、ちゃんとインストールできましたね。

使い方

適当にdocker-composeでDBを起動しておきます。

以下のdocker-composeファイルを作成しておきます。

version: '3'

services:
  db:
    image: postgres:15
    container_name: postgres
    ports:
        - 5432:5432
    environment:
        - POSTGRES_PASSWORD=example

これを起動して、今回使用するDBも適当に作っておきましょう。

(今回は「psqldef」という名前で作りました)

% psql -h localhost -p 5432 -U postgres  
Password for user postgres: 
psql (14.9 (Ubuntu 14.9-0ubuntu0.22.04.1), server 15.4 (Debian 15.4-2.pgdg120+1))
WARNING: psql major version 14, server major version 15.
         Some psql features might not work.
Type "help" for help.

postgres=# create database psqldef;
CREATE DATABASE

まずはDBの管理に使用する「schema.sql」を作成します。

touch schema.sql

作成したファイルにUserテーブルを作成するクエリを書いておきます。

CREATE TABLE public.user (
    id bigint NOT NULL,
    name text,
    age integer
);

ではこれを先程作成したデータベースに適用していきます。

% psqldef --password=example -h localhost -p 5432 -U postgres psqldef < schema.sql
-- Apply --
CREATE TABLE public.user (
    id bigint NOT NULL,
    name text,
    age integer
); 

さて、これでユーザーテーブルが作成されました。

psqldef=# \dt
        List of relations
 Schema | Name | Type  |  Owner   
--------+------+-------+----------
 public | user | table | postgres
(1 row)

次に、差分が変更されるかも確認してみましょう。

IDにUUIDが使えるようにしたのと、名前に制約を追加しましょう。

CREATE EXTENSION "uuid-ossp";

CREATE TABLE public.user (
    id uuid NOT NULL DEFAULT uuid_generate_v4(),
    name varchar(255),
    age integer
);

この状態で再度マイグレーションを実行してみましょう。

% psqldef -h localhost -U postgres --password=example psqldef < schema.sql
-- Apply --
CREATE EXTENSION "uuid-ossp";
ALTER TABLE "public"."user" ALTER COLUMN "id" TYPE uuid;
2023/10/07 17:09:37 pq: column "id" cannot be cast automatically to type uuid

自動でALTER TABLEが実行されていますが、失敗してしまいました。

もとのデータをキャストすることができないので、以下のようなALTER TABLEが実行されないとダメですね。

ALTER TABLE "public"."user" ALTER COLUMN "id" SET DATA TYPE UUID USING (uuid_generate_v4());

これを手動で実行してあげて、再度マイグレーションを実行します。

% psqldef -h localhost -U postgres --password=example psqldef < schema.sql
-- Apply --
ALTER TABLE "public"."user" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4();
ALTER TABLE "public"."user" ALTER COLUMN "name" TYPE varchar(255);

残りのマイグレーションが実行されました。

まとめ

もちろん、上記のようにIDをintから途中でUUIDにするようなDB設計はあまりいいとは言えません。

しかし、複数環境を運用する場合でこういったエラーが出た場合はどうすればよいでしょうか?

一応--before-applyというオプションを使用すれば事前に適用可能なSQLの指定ができるので、エラーが起こったらそのたびに--before-applyとして適用できるSQLを用意するという対応策は考えられますね。

先の例であれば、以下のような差分SQLを用意する必要があります。

CREATE EXTENSION "uuid-ossp";
ALTER TABLE "public"."user" ALTER COLUMN "id" SET DATA TYPE UUID USING (uuid_generate_v4());

その上で、これを以下のように--before-applyで指定してあげます。

# --before-applyにはSQL文をそのまま書く必要がある
psqldef -h localhost -U postgres --password=example --before-apply="$(cat diff.sql)" psqldef < schema.sql

また、差異SQLの管理はこちら側で行わなければいけませんが、これは必要であれば行うでよいでしょう。

Prismaと違ってPostgresのテーブルポリシーが書けるのは結構嬉しいです。

さくっとマイグレーションをしたいならこれ、ちゃんとやりたいならPrismaみたいな使い分けがいいかも?

参考