Claude CodeでPL/SQLのストアドを読解する — ALL_SOURCEから呼び出し関係を表にする
Oracleのストアドパッケージを、DBに接続させずに読む手順です。ALL_SOURCEを書き出したファイルだけを渡し、呼び出し関係・更新テーブル・例外の握りつぶしを表にします。
引き継いだOracleの業務システムに、数十本のストアドパッケージが残っている。仕様書はなく、どのパッケージがどのテーブルを更新するのかも分からない。この状況でClaude Codeに読ませるときは、DBへ接続させずにソースをファイルで渡すのが安全です。
この記事では、ALL_SOURCE をSQL*Plusで書き出し、パッケージ間の呼び出し・更新するテーブル・例外の握りつぶし箇所を1枚の表にする手順を書きます。ora2pgによる移行変換はOracleからPostgreSQLへの移行で扱っており、ここは「読んで把握する」までが範囲です。
なぜDBではなくソースファイルを渡すのか
結論は単純で、Claude Codeに渡す権限を「読み取り専用のテキスト」まで絞れるからです。
DBに接続できる状態のエージェントには、2つの面倒がつきまといます。接続情報を預けること、そして読むだけのつもりが更新系のSQLを流せてしまうことです。ファイルだけを渡す構成なら、どちらも最初から起きません。
Oracleの接続をClaudeにつなぐ方法そのものはClaudeからOracle MCPサーバーを使うにまとめています。今回はあえてその逆で、MCPもDB接続も設定しません。
DB接続で読ませる場合と、ファイルで渡す場合
DBに接続させる
Claude Code側で接続情報を扱うことになります。更新系のSQLを防ぐには、接続ユーザーの権限か別の仕組みで縛る必要があります。
書き出したファイルを渡す
DBに触れるのは人間の書き出し1回です。Claude Codeはワーキングディレクトリのテキストを読むだけで済みます。
書き出すもの: ソース本体と依存関係の2種類
書き出す対象は2つです。
| 書き出す対象 | 元のビュー | 何に使うか |
|---|---|---|
| パッケージのソース | 元のビューALL_SOURCE | 何に使うかClaude Codeが読んで表を作る材料 |
| 依存関係の一覧 | 元のビューALL_DEPENDENCIES | 何に使うか表を検算するための答え合わせ |
ALL_SOURCE は、現在のユーザーがアクセスできるストアドオブジェクトのソーステキストを表すビューです。TYPE 列には PACKAGE や PACKAGE BODY、PROCEDURE、FUNCTION、TRIGGER などが入ります。LINE 列がソースの行番号、TEXT 列が1行分の本文です。
ALL_DEPENDENCIES は、プロシージャ・パッケージ・ファンクション・パッケージ本体・トリガーの間の依存関係を表します。NAME と REFERENCED_NAME の組が「何が何を参照しているか」で、DEPENDENCY_TYPE は REF か HARD です。
2つ書き出す理由は、Claude Codeの読解と辞書の依存関係を突き合わせるためです。後の「検算」の節で使います。
SQL*Plusで書き出す
スキーマ名は APP と仮置きします。パッケージ本体(PACKAGE BODY)を1本1ファイルで出すのが読みやすい形です。出力先の src/ ディレクトリは、SQL*Plusを起動するフォルダーに先に作っておきます。
TEXT 列は行末に改行を含むことが多く、そのまま書き出すと1行ごとに空行が挟まります。RTRIM(text, CHR(10)) で末尾の改行を落としてから出力します。
-- export_src.sql (人間が SQL*Plus で実行する)
SET HEADING OFF FEEDBACK OFF PAGESIZE 0 LINESIZE 4000 TRIMSPOOL ON
SET TERMOUT OFF
SPOOL src/PKG_ORDER.pkb REPLACE
SELECT RTRIM(text, CHR(10)) FROM all_source
WHERE owner = 'APP' AND name = 'PKG_ORDER' AND type = 'PACKAGE BODY'
ORDER BY line;
SPOOL OFF
SPOOL src/PKG_STOCK.pkb REPLACE
SELECT RTRIM(text, CHR(10)) FROM all_source
WHERE owner = 'APP' AND name = 'PKG_STOCK' AND type = 'PACKAGE BODY'
ORDER BY line;
SPOOL OFFSPOOL は、表示された結果をファイルに書き出すコマンドです。REPLACE は既存ファイルを置き換え、SET TERMOUT OFF はスクリプト内の出力を画面に出さずに済ませます。ファイル名に拡張子を付けなければ既定の拡張子が補われるので、.pkb のように明示しておくと迷いません。
パッケージが多いときは、名前の一覧を SELECT DISTINCT name FROM all_source WHERE owner='APP' AND type='PACKAGE BODY' で出し、SPOOL の行をその一覧から生成します。仕様部(PACKAGE)も同じ要領で .pks に出しておくと、公開しているプロシージャの一覧が読みやすくなります。
依存関係は1ファイルにまとめます。
SET HEADING ON PAGESIZE 0 LINESIZE 400 COLSEP ',' TRIMSPOOL ON
SPOOL src/dependencies.csv REPLACE
SELECT name, type, referenced_owner, referenced_name, referenced_type,
dependency_type
FROM all_dependencies
WHERE owner = 'APP'
AND type IN ('PACKAGE BODY', 'PROCEDURE', 'FUNCTION', 'TRIGGER')
ORDER BY name, referenced_name;
SPOOL OFF書き出した行数をLINE列と突き合わせる
読解の根拠は「何行目の記述か」です。ファイルの行番号と ALL_SOURCE の LINE が一致していれば、Claude Codeが示す行番号をそのまま元のソースの行に戻せます。
書き出した直後に、次の2つが一致するかを確かめます。
wc -l src/PKG_ORDER.pkbSELECT MAX(line) FROM all_source
WHERE owner = 'APP' AND name = 'PKG_ORDER' AND type = 'PACKAGE BODY';数が違うなら、出力の途中に空行や見出し行が混ざっています。そのまま読ませると、行番号がずれた「根拠」が表に載ります。まず RTRIM の付け忘れと PAGESIZE 0 の指定を見直し、書き出し直してください。
落とし穴
`WRAP`で隠されたソースは読めない
wrap で難読化されたパッケージは、書き出したファイルを開いても本文を読めません。そのパッケージは表に「読めない」と書き、呼び出し関係は dependencies.csv の側で補います。
Claude Codeに渡す前の権限設計
次に、読ませる側の囲いを作ります。Claude Codeの権限ルールは、モデルではなくClaude Code自身が強制します。プロンプトやCLAUDE.mdに「DBには接続しない」と書くだけでは、許可の範囲は変わりません。
プロジェクトの .claude/settings.json に、DB接続系のコマンドと資格情報のパスを deny で入れておきます。
{
"permissions": {
"deny": [
"Bash(sqlplus *)",
"Bash(sql *)",
"Bash(tnsping *)",
"Read(./.env)",
"Read(./wallet/**)"
]
}
}ルールはdeny、ask、allowの順に評価され、先に当たったものが結果になります。deny に入れたコマンドは、より狭い allow があっても止まります。Read(./.env) のようにパスを指定すれば、ファイル系のツールによる読み取りを拒否できます。
一方で、Bash の deny は書いたコマンドの形に対するルールです。ルールの網羅に頼るより、DBに到達できるクライアントや接続情報をそもそも作業環境に置かないほうが確実です。書き出し用のSQL*Plusは、Claude Codeを動かす環境とは別の端末で実行してください。
表を作らせるプロンプト
src/ に .pkb と dependencies.csv が揃ったら、Claude Codeを起動します。新しいコードベースを理解するときは、全体の概要から入って個別の部品へ掘り下げる流れが向いています。ここでもその順序で進めます。
src/ の PL/SQL パッケージ本体を読み、次の3つの表を docs/plsql/overview.md に作って。
1. パッケージ間の呼び出し関係 (呼び出し元.プロシージャ → 呼び出し先.プロシージャ)
2. プロシージャごとの対象テーブルと操作 (SELECT / INSERT / UPDATE / DELETE / MERGE)
3. 例外の握りつぶし (WHEN OTHERS で再 RAISE せず NULL・ログのみで抜ける箇所)
全行に「ファイル名:行番号」を付けること。ソースに無いものは推測せず「不明」と書くこと。
DB への接続や SQL の実行はしない。指示の要点は3つあります。
- 表の定義を先に決める: 呼び出し・更新・握りつぶしの3種類に絞ると、答えが比べやすくなります
- 行番号を必須にする: 後で人間が元のソースを開いて確かめられます
- 「不明」と書かせる: 書いていないことを推測で埋められる余地を残しません
出力の形
表は、例えば次のような形にまとまります。以下は書式の例で、実在するパッケージの出力ではありません。
| プロシージャ | 呼び出す先 | 更新テーブル | 根拠 |
|---|---|---|---|
| PKG_ORDER.CONFIRM | PKG_STOCK.RESERVE | ORDERS (UPDATE) | PKG_ORDER.pkb:212, :230 |
| PKG_STOCK.RESERVE | (なし) | STOCK (UPDATE), STOCK_LOG (INSERT) | PKG_STOCK.pkb:88, :95 |大きなパッケージはサブエージェントに分ける
数千行のパッケージを何本も順番に読ませると、ファイルの内容でコンテキストがすぐ埋まります。大きなコードベースの調査は、サブエージェントに任せられます。読み込みは別のコンテキストで行われ、親には要約だけが戻ります。
src/ のパッケージごとにサブエージェントを使って、上記の3つの表を作って。
各サブエージェントは自分の担当パッケージだけを読み、表の行だけを返すこと。
最後に親でまとめて docs/plsql/overview.md を作る。パッケージ間の呼び出しは、担当外のパッケージ名が出てきても「呼び出し先の名前」を記録するだけで足ります。呼び出し先の中身を読むのは、そのパッケージの担当に任せます。まとめの段階で、呼び出し元と呼び出し先の表が両側から埋まっているかを見ます。
非対話で回したい場合は、claude -p にファイルを渡す形も取れます。ただし、まず対話で1本ぶんの表の出来を確かめてから量を増やしてください。
表を鵜呑みにしないための検算
出てきた表は、そのまま信用しません。3つの方法で検算します。
方法1: 依存関係CSVと突き合わせる
dependencies.csv の REFERENCED_NAME に出てくるのに、表の2「対象テーブル」に無い名前を探させます。
docs/plsql/overview.md の「対象テーブル」と src/dependencies.csv の
REFERENCED_NAME を比べて、片方にしか無い名前を一覧にして。
それぞれ、ソースのどこに記述があるか (または無いか) も調べて。差分が出た場所は、読み落としの候補です。動的SQLで組み立てたテーブル名など、ソースの文字列を追わないと見えない参照が混じっていることがあります。逆に、辞書にあってソースから見つからない名前は、別のオブジェクト経由の依存の可能性があります。どちらも最終的には人が元のソースを開いて判断します。
方法2: 握りつぶしはgrepで母数を出す
握りつぶしの判定は、Claude Codeの読解と機械的な検索のどちらにも頼れます。まず母数を grep で出し、表の件数と比べます。
grep -n -i "when others" src/*.pkbWHEN OTHERS の件数が表の握りつぶしより多いのは自然です。再 RAISE しているものは握りつぶしではないからです。ただし、表に載っていない WHEN OTHERS が何件あるかは、1件ずつ「再RAISEしているか」を確認してください。
方法3: 行番号をランダムに抜き打ちで開く
表の10行ほどを無作為に選び、「ファイル名:行番号」が実際にその内容になっているかを開いて確かめます。1件でも外れたら、その表は行番号の信頼性がないものとして作り直します。
落とし穴
行番号の根拠は書き出し直後に確かめる
ソースを更新して書き出し直したのに古い表を使うと、行番号が別の箇所を指します。ソースを入れ替えたら、表も作り直してください。
CLAUDE.mdに読み方の規約を置く
何本も読むうちに、表の列や書式がぶれていきます。リポジトリの CLAUDE.md に読み方を書いておくと、パッケージが替わっても同じ型で表が出ます。
# PL/SQL 読解の規約
- 対象は src/*.pkb と src/dependencies.csv。DB には接続しない。
- 表の行には必ず「ファイル名:行番号」を付ける。ソースに無いことは「不明」。
- 更新対象は SELECT / INSERT / UPDATE / DELETE / MERGE の別を書く。
- WHEN OTHERS は、再 RAISE していれば握りつぶしに数えない。
- 動的SQL (EXECUTE IMMEDIATE) は、組み立てている文字列の行番号を残す。この規約は、読解を何日かに分けて進めるときにも効きます。前日の表と今日の表で、列の意味が揃っているからです。
読解を終えたあとの使い道
表ができると、保守や移行の最初の判断が軽くなります。
- 影響範囲の見積もり: テーブルの列を変えるときに、更新側のプロシージャを表から引けます
- 握りつぶしの優先順位付け: 失敗が表に出ない箇所を、テストの手薄な場所として先に洗えます
- 移行の分割: 呼び出し関係を見れば、依存の少ないパッケージから移す順序が決まります。変換そのものはora2pgの変換を直す手順に引き継げます
古いスクリプトを読んで仕様とテストに起こす流れは、Perlの古いスクリプトの読解でも同じ型で扱っています。
まとめ
DBに接続させないことが、この手順の核です。書き出しは人間が1回、読解はファイルだけ、検算は辞書の依存関係とgrepで。この分担なら、Claude Codeに渡す権限は読み取りだけで足ります。
最初の1本は、短いパッケージで表の列と行番号の付け方を確かめてください。その型が固まってから、サブエージェントで全体に広げるのが無理のない順序です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。