Prisma Postgresに既存DBのデータをインポートする

先日Prisma Postgresのearly accessが発表されました。Prismaからの利用に制限されますが、従量課金制のPostgreSQLサーバーレスDBとして利用することができるようです。個人開発で固定費用なしでRDBを使うことができるのはとても嬉しいですね。

ですがpsqlでの接続ができないので、pg_dumpの出力をpsqlで取り込むお決まりの手順が使えず、既存のデータをインポートするのにはちょっと手間が必要です。
この記事を書いている2024/11/02時点ではデータのインポート、エクスポート手段は公式に用意されてはいないようでした(まだearly accessですしね)


以下のdiscussion内容を参考にpg_dumpで元DBからdumpを出力し、改変しつつ生SQLとして読ませるseedスクリプトを用意することでインポートに成功しました。


無事成功!



以下は移行にあたっての記録です。

Prisma Posgresの登録

https://www.prisma.io/postgres からログインして登録していく。クレジットカードの登録は不要。
webからぽちぽち登録していけば用意ができる










東京リージョンが用意されている...!




Generate database credentialsをクリックすると接続情報が生成される。接続情報はDATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=xxxx"という形式になっており、独自プロトコル+api_key指定での接続なのでpsqlなど従来のPostgreSQL用のツールは使えない。
念の為psqlでの接続を試してみたけどやはり接続できなかった。

運用中のDBのデータをインポートする

この方針でなんとかなるんじゃないかとあたりをつけて試してみる。

まずはスキーマを元DBから取得して適用してやる。これはprismaに用意されている機能で充足する。以下はPrisma Postgres環境にデータを入れる前にローカルのPostgreSQLにデータを入れてやる手順で行っている。
  1. 元DBからprisma db pull
  2. ローカルDBに向けてprisma db migrate dev
  3. Prisma Postgresに向けてprisma db migrate deploy
1番目の手順でschema.prismaを2番目の手順でローカルDBへのスキーマ適用と同時にマイグレーションファイルを生成している。なお、各環境への向き先の変更は.envファイルのDATABASE_URLを書き換えることで行った。

最後にデータを元DBから取得してインポートする。ここが一手間かかったポイント。
  1. 元DBからpg_dump
  2. seedスクリプトを用意
  3. ローカルDBに向けてprisma db seedでインポート
  4. Prisma Postgresに向けてprisma db seedでインポート
prismaには生クエリを実行する機能がありますが、試したところデフォルトでpg_dumpから出力されるクエリに含まれているCOPY文が通らないようでした。冒頭で紹介したdiscussionの通り、以下のような--column-insertsオプションをつけてINSERT文を生成してやるとよさそうです。

pg_dump --no-publications --no-security-labels --no-subscriptions -O --no-tablespaces --no-comments -a --column-inserts --attribute-inserts 接続情報
prisma/seed.tsとして用意したものは以下のような内容です。
import { PrismaClient } from '@prisma/client'
import { readFileSync } from 'fs'

const prisma = new PrismaClient()

async function main() {
    const rawQuery = readFileSync('<ダンプファイルのパス>')
        .toString()
        .split('\n')
        .filter((line) => !line.startsWith('--')) // remove comments-only lines
        .join('\n')
        .replace(/\r\n|\n|\r/g, ' ') // remove newlines
        .replace(/\s+/g, ' ') // excess white space

    for (const line of splitStringByNotQuotedSemicolon(rawQuery)) {
        const result = await prisma.$executeRawUnsafe(line)
        console.log({ line, result })
    }
}

function splitStringByNotQuotedSemicolon(input: string): string[] {
    const result: string[] = [];

    let currentSplitIndex = 0;
    let isInString = false;
    for (let i = 0; i < input.length; i++) {
        if (input[i] === "'") {
            // toggle isInString
            isInString = !isInString;
        }
        if (input[i] === ';' && !isInString) {
            result.push(input.substring(currentSplitIndex, i + 1));
            currentSplitIndex = i + 2;
        }
    }

    return result;
}


main()
    .then(async () => {
        await prisma.$disconnect()
    })
    .catch(async (e) => {
        console.error(e)
        await prisma.$disconnect()
        process.exit(1)
    })
1件ずつINSERT文を投げるので全件完了するまで時間はかかりましたが、無事にデータをインポートすることができました!

コメント

このブログの人気の投稿

PostgreSQLで多次元配列を1次元配列に展開したい

inotify でファイル監視しようず!

ジャックパーセルのかかとの内側を直した