既存のデータベースでPrisma migrateする。しかもPostGISを使う

既存のデータベースをPrismaマイグレーションできるようにしたくなった。理由はいろいろあるがやはりローカル環境 → 開発環境 → ステージング環境 → 本番環境へとDBの定義を反映していくのが手作業はさすがにないなと思えてきたからだ。もちろん実際には毎回SQLを直接手で入力なんてことはないだろうけど。

あとは他の人が開発するのにDBのセットアップをする際にも楽だ。

というわけで導入しようとしたのだがそれなりに悩んだりしたのでメモを残しておく。

前提

  • すでに存在しているDBをPrismaのmigrationツール管理下におく
  • これまでは直接DBにDDL文を実行して定義などしていた
  • DBの定義はprisma db pullを実行してsyncしていた

やっていく

まずは既存のDBの定義とPrismaのモデルを同期します。普段からやってるものの念の為。

prisma db pull

同期したら早速マイグレーションのコマンドを実行します。以下ではDBのURLをマスクしてます。

prisma migrate dev --name initial-migration --create-only

✔ We need to reset the PostgreSQL database "postgres" at "xxxxxxxxxx.cluster-xxxxxxxxxxx.us-east-1.rds.amazonaws.com:5432".
Do you want to continue? All data will be lost. … yes

--nameはこのマイグレーションをわかりやすく識別するためにつける任意の名前。--create-onlyというのはマイグレーションファイルの作成だけで実際の適用はしないというオプション。

さて、こんな感じで実行すると不穏なメッセージが表示されている。Yesを選択して進めるとメッセージの通り、この時点で既存のDBのテーブルとデータは全て消えてしまう。--create-onlyにしているにも関わらず、だ。

Prisma migrationを導入しようとすると、最初のタイミングでmigration用の管理テーブルが作られる。そして手動で定義していたものについてはその時点で既存のテーブルは一度削除されてしまうという仕様のようだ。テーブルが削除されるのでもちろんデータも消える。開発環境とは言えそれなりの量のデータが入っていること、本番適用時にデータが全部消えてしまうのは論外なこともあり色々と調べたり試したりしたものの良い解決策は見つからなかった。そう、消えるのだ。--create-onlyを指定したところで消えるのだ。この場合の--create-onlyは確かに適用まではされない。

--create-onlyをつけて実行すると実行日時のフォルダとともにマイグレーションファイルができあがり、マイグレーションの管理テーブルだけが作成された状態でマイグレーションファイルの内容は反映されていない状態となる。大事なことなので2回言うが、DB上は全部のテーブルが削除された上でマイグレーションの管理テーブルのみが存在しているという状態になる。

これに関してはどうしようもないので既存のものに導入する場合、かつデータも残したい場合は事前にバックアップするなり、テーブル単位でエクスポートしておくことは必須といえる。

PostGISをどう扱うか

さて、自分のようにPostGISを使っている場合はそのまま実行すると失敗する。なぜならば、先の処理で一度DBの中身がまっさらになっていてPostGISのセットアップについてもリセットされた状態になるからだ。

これは作成したマイグレーションファイルの先頭に以下を追加することで対応できる。これは初回のマイグレーションファイルだけでいい。なお、このようにマイグレーションファイルを直接的に修正していいのは適用前のものに対してだけですでに適用したマイグレーションファイルの修正は禁物だ。

CREATE EXTENSION postgis;
CREATE EXTENSION fuzzystrmatch;
CREATE EXTENSION postgis_tiger_geocoder;
CREATE EXTENSION postgis_topology;
ALTER SCHEMA tiger OWNER TO rds_superuser;
ALTER SCHEMA tiger_data OWNER TO rds_superuser; 
ALTER SCHEMA topology OWNER TO rds_superuser;
CREATE FUNCTION exec(text) returns text language plpgsql volatile AS $f$ BEGIN EXECUTE $1; RETURN $1; END; $f$;
SELECT exec('ALTER TABLE ' || quote_ident(s.nspname) || '.' || quote_ident(s.relname) || ' OWNER TO rds_superuser;')
  FROM (
    SELECT nspname, relname
    FROM pg_class c JOIN pg_namespace n ON (c.relnamespace = n.oid) 
    WHERE nspname in ('tiger','topology') AND
    relkind IN ('r','S','v') ORDER BY relkind = 'S')
s;

そしてPostGISのgeometry型をPrismaはサポートしていない。したがってここについても修正しておく必要がある。実際には以下のように単なるgeometry型として生成されている。

CREATE TABLE "sample" (
    "id" SERIAL NOT NULL,
    "name" TEXT,
    "coordinates" geometry,

    CONSTRAINT "sample_pkey" PRIMARY KEY ("id")
);

これを以下のようにGeometryのタイプも指定する形に修正しておく。ここではPOINTを指定すると同時に座標系も4326を指定している。

CREATE TABLE "sample" (
    "id" SERIAL NOT NULL,
    "name" TEXT,
    "coordinates" GEOMETRY(POINT,4326),

    CONSTRAINT "sample_pkey" PRIMARY KEY ("id")
);

追加・修正したらマイグレーションを実行する。

データを戻す

今回のように--create-onlyで実行したあとに適用する場合はprisma migrate devを実行するだけでいい。なお、コマンドを見てわかるようにこの手順は開発環境向けであって本番適用手順はまた別だ。

prisma migrate devを実行したら必要に応じて先程のバックアップからデータを戻す。なぜならば前述のとおり一度テーブルが削除されているためデータが空っぽになっているからだ。自分の場合はpg_dumpを使ってテーブル単位でデータだけバックアップをとっておいてそれをインポートする方式で対応した。

2回目以降

2回目以降も同様にprisma migrate dev --name <名前>を実行していく。だが、個人的には毎回--create-onlyをつけてマイグレーションファイルの中身を目視で確認するのをおすすめしたい。

そして、2回目以降も実行ごとに日時のフォルダとマイグレーションファイルが作成される。

その他

さて、このようにPrismaによるMigrationを導入したあとはDBの定義に関して基本的にはschema.prismaで定義を行ってはマイグレーションを実行するという流れになる。仮にDB側で直接テーブルを作ったりするとprisma migrate resetが必要になってしまい、またテーブルがまるっと削除されてしまうので要注意だ。

また、前述のPostGISに限らず生成されたSQLが意図したものではない場合や望ましくない場合があるだろう。そんなときもマイグレーションファイルを修正して実行すればいいのでやはり--create-onlyをつけて確認するというのをおすすめしたい。

例えば列名の変更は通常は以下のようなSQLで実行できる。

ALTER TABLE テーブル RENAME COLUMN 旧列名 TO 新列名

だが、これをPrismaでやろうとしてschema.prismaのモデル上で変更すると以下のようなSQLが生成されてしまう。

ALTER TABLE テーブル名 DROP COLUMN 旧列名,
ADD COLUMN     新列名 TEXT;

そう、この場合だと旧列名の列を削除して新しい列名の列を追加するというSQLが発行されてしまう。つまりデータも消える。これはよくない。

そんなときにマイグレーションファイルの中身を修正して実行してあげれば問題ない。

ついでにPostGISを使っている場合にGeometry型の列に対してgistインデックスを張っている場合もあると思う。これに関してもPrismaでは対応していないのでまずは普通のインデックスを張るようにschema.prismaで設定した上で--create-onlyを実行する。作成されたマイグレーションファイルでは当然ながら普通のbtreeなインデックスが作成されるようになっているのでこれをgistインデックスに手動で書き換える。

具体的には、列指定の前にUSING gistを追加してあげるだけでいい。以下のように。

CREATE INDEX "idx_geo" ON "sample" USING gist("geo");

まとめ

というわけでPostGISを使っている場合は少し手を入れる必要はあるが、基本的には問題なくPrismaによるマイグレーションができる。そしてPrismaを信じきらず必ず--create-onlyで適用前に確認することをおすすめする

©Keisuke Nishitani, 2023   プライバシーポリシー