Claude Media
OracleからPostgreSQLへの移行をClaude Codeで進める — ora2pgの変換を直す

OracleからPostgreSQLへの移行をClaude Codeで進める — ora2pgの変換を直す

ora2pgで変換したPL/SQLの残りをClaude Codeで直し、件数と合計の照合SQLで移行前後の一致を確かめる手順です。パッケージ、NULLと空文字、数値型の3点を扱います。

OracleのPL/SQLをPostgreSQLへ移すとき、ora2pgは変換の大半を自動で済ませます。ただし、出力されたPL/pgSQLがそのまま業務の結果を再現する保証はありません。残りの手直しをClaude Codeに任せ、直したあとの正しさは件数と合計の照合SQLで確かめる、という分担が現実的です。

この記事は、その分担の作り方を順に示します。Claudeに「直す」作業と「検算を書く」作業を渡し、一致の判定は人間が読める数字に寄せます。

ora2pgが自動で済ませる範囲と、手直しが残る範囲

ora2pgは、Oracle(とMySQL)に接続して構造やデータを取り出し、PostgreSQLへ読み込めるSQLスクリプトを生成するオープンソースの移行ツールです。移行評価レポート、スキーマ変換、PL/SQLからPL/pgSQLへの変換、データ移行(オフライン、停止を伴う)、データ検証が機能として挙げられています。

ただしOracle固有のPL/SQLについて、公式のドキュメントは次のように書いています。関数、プロシージャ、パッケージ、トリガーの生成コードは、PostgreSQLの構文に合わせて人間が見直す必要があります。PL/SQLの自動変換は発展途上で、手作業が出る前提です。

つまり手直しの対象は最初から決まっています。Claude Codeに渡すのは、その見直し作業です。

工程ora2pgの役割Claude Codeの役割人間の役割
抽出と変換ora2pgの役割Oracleから取り出して生成Claude Codeの役割なし人間の役割設定ディレクティブの選択
構文エラーの解消ora2pgの役割なしClaude Codeの役割psqlのエラーを読んで修正人間の役割差分のレビュー
検算SQLの作成ora2pgの役割TEST系の比較機能Claude Codeの役割両DB用の同一クエリを書く人間の役割合否の判定

作業ディレクトリと、Claudeに守らせる規約

ora2pgには、移行作業用のツリーを作るオプションがあります。

ora2pg --project_base /app/migration/ \
  --init_project test_project

生成されるツリーの要点は2つです。sources/にOracleの元コード、schema/にPostgreSQL向けに変換した結果が入ります。config/には設定ファイル、reports/には移行コストの評価レポートが置かれます。

この分け方は、Claudeへの指示にそのまま使えます。元コードは読むだけ、直すのは変換後だけ、という線引きです。プロジェクトのCLAUDE.mdに書いておくと、毎回の指示で言い直さずに済みます。

## Oracle→PostgreSQL移行のルール
- sources/はOracleの元コード。読むだけで、編集しない
- 直すのはschema/配下。1ファイル直すごとにpsql -fで流す
- 関数は、checks/のケース表と同じ入力でOracle側と同じ値が出るまで完了にしない
- NULLと空文字、数値の丸めに関わる変更は、必ず理由をコメントに書く

Claude Codeの公式ドキュメントは、CLAUDE.mdに書く対象を「Claudeが同じ間違いを2回したとき」「毎回チャットで同じ訂正を打つとき」と説明しています。移行では、同じ型の変換ミスが何十ファイルにも現れます。1回目に直した時点で規約へ足す運用が向いています。

接続情報の扱いも先に決めます。ora2pgの設定ファイルには、Oracleへの接続先を定義します。Claudeに読ませる必要はないので、プロジェクトの.claude/settings.jsonに拒否ルールを書きます。

{
  "permissions": {
    "deny": ["Read(./config/ora2pg.conf)"]
  }
}

権限ルールは「拒否、確認、許可」の順に評価され、最初に一致したものが結果になります。拒否に入れた対象は、許可ルールを足しても通りません。

このルールが効く範囲には限りがあります。組み込みのファイルツールと、cat・head・sedのようにBashで認識される読み取りコマンドには及びます。一方、ファイル名を指定せずに読むコマンド(grep -rなど)や、PythonやNodeのスクリプトが自分でファイルを開く場合は対象外です。接続情報を確実に守りたいときは、Bashの実行を許可しないか、サンドボックスを有効にします。

変換結果で見直す4つの穴

手直しが残りやすいのは、パッケージ、NULLと空文字、数値型、暗黙の型変換の4つです。それぞれ、ora2pgの設定と生成物の両方を見ます。

パッケージは、スキーマにするか関数名にするか

Oracleのパッケージは、既定ではPostgreSQLのスキーマとして再現されます。PACKAGE_AS_SCHEMAを無効にすると、パッケージ名と関数名を連結した単独の関数になり、package_name.function_name()の呼び出しはpackage_name_function_name()に置き換えられます。

どちらを選ぶかで、アプリケーション側の呼び出しコードの書き換え方が変わります。Claudeに頼む前に、方針を一つに決めてください。決めないまま直させると、ファイルごとに別の命名が混ざります。

この置換が働くのは、ora2pgのPL/SQL変換が読み込むDDLとコードです。ora2pgが読まないアプリ側のコードは、自分で書き換えます。呼び出し元の洗い出しは、次のようにClaudeに頼めます。

アプリのコードからpkg_billingの関数呼び出しを全部探して、
schema/packages/pkg_billingの関数名と1対1で対応表にして

NULLと空文字は、挙動が変わる

Oracleは空文字をNULLと同じに扱います。PostgreSQLは両者を区別します。ora2pgにはNULL_EQUAL_EMPTYというディレクティブがあり、NULL判定をcoalesceを使った式に置き換えて、Oracleの挙動を真似られます。

前と後

NULL判定の書き換え(公式の説明に沿った形)

BEFORE

Oracle(元のPL/SQL)

IF p_note IS NULL THEN
  ...
AFTER

NULL_EQUAL_EMPTY有効時の変換

IF (coalesce(p_note::text, '') = '') THEN
  ...

ここで引っかかるのは、公式がこの置換を最善とは言っていない点です。アプリケーションを自分で制御できるなら、空文字をNULLに変換するほうがよいと書かれています。

置換を有効にすれば、元の挙動に近づきます。空文字をNULLへ寄せる直し方は、PostgreSQLらしい書き方になります。どちらを採るかは移行の方針なので、Claudeに決めさせず、CLAUDE.mdに一行で書いておきます。

数値型は、丸めが入る

OracleのNUMBER(p,s)は、既定ではPostgreSQLのrealやfloatに近似変換されます。金額のように桁を保ちたい列では、ora2pgのPG_NUMERIC_TYPEを0にすると、numeric(p,s)のまま保てます(既定の1が近似変換です)。numericはrealやdoubleより遅いとも書かれています。

この丸めが、後で件数ではなく合計に出ます。次の節の検算が合計を見るのは、このためです。

暗黙の型変換は、エラーより値のずれで表れる

文字列と数値の比較や、日付と文字列の突き合わせのように、型の変換を言語任せにしていた箇所は要注意です。構文エラーなら変換直後のpsqlで見つかります。エラーにならず値だけが変わる場合は、同じ入力で両側を動かさないと分かりません。

暗黙の型変換の結果が一致するかは、変換後のコードを同じ入力で動かすまで分かりません。そのため検算ケースを用意します。

Claude Codeで直す順番

直す作業は、一度に全ファイルを渡さず、小さい単位に区切ります。Claude Codeの公式ドキュメントも、リファクタリングは「小さくテスト可能な増分」で行うよう勧めています。

手順

1ファイルを直す流れ

  1. 1

    エラーを出す

    schema/functions/の1ファイルをpsqlで流し、エラー出力を保存します。

  2. 2

    エラーだけを渡す

    エラーとファイルをClaudeに渡し、そのファイルだけの修正を頼みます。

  3. 3

    差分を読む

    直した箇所をgit diffで人間が読みます。NULLと数値の変更は特に確認します。

  4. 4

    検算ケースを流す

    次の節の照合SQLで、Oracle側と同じ値かを確かめます。

エラーの解消だけなら、非対話モードで繰り返せます。claude -pは標準入力や引数のプロンプトを一度だけ処理して終了し、--allowedToolsで承認なしに使えるツールを指定できます。

for f in schema/functions/*.sql; do
  psql -v ON_ERROR_STOP=1 -f "$f" 2> err.txt || \
    claude -p "$f のエラー(err.txt)を直して。\
sources/は編集しない" \
      --allowedTools "Read,Edit"
done

このループは「流す、落ちたら直す」だけを自動化します。直した結果が正しいかは判定しません。判定は次の照合SQLの仕事です。

件数と合計の照合SQLで検算する

移行前後で同じ入力に同じ結果が返るかは、SQLの結果を並べれば確かめられます。ora2pgにも比較機能があり、TESTはOracleとPostgreSQLのオブジェクトの差、TEST_COUNTは表の行数の差、TEST_VIEWはビューの行数の差、TEST_DATAは両側の行のデータ検証を行います。--count_rowsを付けると、実際の行数を数えます。

TEST_DATAの既定の比較は先頭の10000行で、0を指定すると全行になります。データを変更した後は、行の並びが両側で変わります。主キーかユニーク索引で並べる必要があるため、そのどちらも持たない表は比較できません。

ora2pgの比較で足りない部分、つまり合計とNULLの内訳は、自分で書いた照合SQLで補います。Claudeに頼むのはここです。

表ごとの集計を、両側で同じ形にそろえる

SELECT 'orders' AS tbl,
       COUNT(*)                                  AS row_cnt,
       SUM(amount)                               AS amount_sum,
       SUM(CASE WHEN note IS NULL THEN 1 ELSE 0 END) AS note_null_cnt
FROM orders;

このSQLはOracleでもPostgreSQLでもそのまま動く形にしてあります。FILTER句のような片方だけの構文を避けるのが、Claudeに頼むときの条件です。SQL*Plusとpsqlは既定の出力形式(見出し、桁揃え、数値の書式)が異なるため、両側の出力形式をそろえてから保存し、diffで比べます。形式がそろえば、一致の判定が機械的に済みます。

diff oracle_orders.out pg_orders.out \
  && echo "一致"

行数が合って合計が違えば、数値型の丸めを疑います。行数が合ってnote_null_cntだけ違えば、NULLと空文字の扱いです。

関数は、固定の入力表で動かす

関数やプロシージャは、ケース表を作って同じ入力を流します。ケースは境界を含めます。

SELECT t.case_id,
       pkg_billing.calc_tax(t.amount, t.tax_code) AS tax
FROM   tax_cases t
ORDER  BY t.case_id;

入力にNULL、空文字、端数、桁あふれ近くの金額を含めてください。本番データから抜く場合は、個人情報を落とした抜き出しにします。ケース表の作成自体もClaudeに頼めます。ケースの狙いを人間が先に書くと、ずれにくくなります。

落とし穴

行数が一致しても、合計は一致しないことがある

行数は表の構造だけで決まり、合計は列の型と値で決まります。NUMBERが近似型に変換されると、行数は一致して合計だけが少しずれます。検算は件数だけで終えず、合計を必ず並べてください。

一致しないときの切り分け

切り分け

合計か件数が合わないとき

  1. Q行数は両側で一致しているか?
    答えがいいえのとき

    ロード中の失敗やフィルタを疑い、ora2pgのロードログを見ます。

    答えがはい
  2. Q合計だけが違うか?
    答えがいいえのとき

    金額列の型を確認し、numeric(p,s)に保っているかを見ます。

    答えがはい
  3. QNULLの件数が違うか?
    答えがいいえのとき

    NULL_EQUAL_EMPTYの設定と、空文字の入力経路を見ます。

    答えがはい

関数単位で入力を絞る

ケース表の1行ずつで両側の結果を比べ、ずれた行から原因を特定します。

ora2pgだけで足りるか、別の道具が要るか

ora2pgは、スクリプトで動く移行エンジンとして位置づけられています。開発元のHexaClusterは、同じ作者の企業向け製品としてHexaRocketを案内しており、スキーマ変換の自動化と検証、オンライン移行とロールバックを特徴に挙げています。料金は問い合わせです。

どちらを採るかは、停止できる時間と検証の厳しさで変わります。ora2pgの移行はオフラインで、停止を伴います。この記事の手順は、そのオフライン移行を前提にしています。

移行先のPostgreSQLを触りながら調べるなら、PostgreSQL向けMCPサーバーの使い方が参考になります。元のOracleにClaudeから接続する選択肢は、Oracle MCPサーバーの解説にあります。ただし、移行作業の途中で本番のOracleへ直接つなぐ必要はありません。取り出したsources/を読ませるほうが安全です。

まとめ

ora2pgの出力は、完成品ではなく下書きです。パッケージの扱いとNULLの方針は人間が先に決め、構文エラーの解消はClaude Codeに任せます。合否は件数と合計の照合SQLで決めます。

先に決めておくと、手戻りが減ります。ディレクティブの選択を後回しにすると、ファイルごとに別の直し方が混ざります。移行の完了条件を「ケース表の全行が一致」に置くと、Claudeにも作業の終わりが伝わります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. ora2pg.darold.net/
  2. github.com/darold/ora2pg/blob/master/doc/Ora2Pg.pod
  3. code.claude.com/docs/en/common-workflows
  4. code.claude.com/docs/en/permissions
  5. code.claude.com/docs/en/headless
この記事を共有:XはてブLinkedIn