既存のデータベースをPrismaでマイグレーションできるようにしたくなった。理由はいろいろあるがやはりローカル環境 → 開発環境 → ステージング環境 → 本番環境へとDBの定義を反映していくのが手作業はさすがにないなと思えてきたからだ。もちろん実際には毎回SQLを直接手で入力なんてことはないだろうけど。
あとは他の人が開発するのにDBのセットアップをする際にも楽だ。
というわけで導入しようとしたのだがそれなりに悩んだりしたのでメモを残しておく。
前提
やっていく
まずは既存の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
で適用前に確認することをおすすめする