パッケージを中心に巨大化したPL/SQLコードベースでは、どのパッケージがどれに依存しているのか、どこを直すとどこが壊れるのかを即座に可視化できることが品質とスピードの両立に直結する。Oracleは依存関係をディクショナリに保持しており、ALL_DEPENDENCIESやUSER_DEPENDENCIES、ALL_OBJECTSなどを組み合わせれば、影響範囲の抽出、再コンパイル順序の算出、さらには外部ツールへ渡すグラフ化まで自動化できる。この記事では、依存の基本構造、変更影響の探索、再コンパイル順序の導出、視覚化のためのDOT出力、INVALIDの原因追跡までを一気通貫の手順とコードで示す。
依存メタデータの読み方と前提整理
ALL_DEPENDENCIESは参照側と被参照側の組を行で表す。参照オブジェクトはNAMEとTYPE、相手側はREFERENCED_NAMEとREFERENCED_TYPEに現れる。パッケージは仕様と本体が別オブジェクトで、仕様に触れると下流へ波及しやすい。依存関係をパッケージ単位へ正規化するため、プロシージャや関数などの下位要素は親パッケージ名に丸めて扱うのが可視化の第一歩になる。
-- パッケージを主語に依存を抽出するための正規化ビュー
CREATE OR REPLACE VIEW v_pkg_deps AS
SELECT
NVL(CASE WHEN a.type IN ('PACKAGE','PACKAGE BODY') THEN a.name
WHEN a.type IN ('PROCEDURE','FUNCTION') THEN a.owner||'.'||a.name
ELSE a.name END, a.name) AS from_name,
CASE WHEN a.type IN ('PACKAGE','PACKAGE BODY') THEN 'PACKAGE'
ELSE a.type END AS from_type,
NVL(CASE WHEN a.referenced_type IN ('PACKAGE','PACKAGE BODY') THEN a.referenced_name
WHEN a.referenced_type IN ('PROCEDURE','FUNCTION') THEN a.referenced_owner||'.'||a.referenced_name
ELSE a.referenced_name END, a.referenced_name) AS to_name,
CASE WHEN a.referenced_type IN ('PACKAGE','PACKAGE BODY') THEN 'PACKAGE'
ELSE a.referenced_type END AS to_type
FROM all_dependencies a
WHERE a.owner = USER
AND a.name <> a.referenced_name
AND a.referenced_owner IS NOT NULL;
変更影響の探索と下流に広がる依存の列挙
特定パッケージを起点に、呼び出し側へ伝播する影響範囲を再帰問い合わせで抽出すれば、仕様変更時にどこまで動作確認が必要かが即座にわかる。次のSQLは起点からの下流の依存を階層的に列挙し、重複を避けるために経路を保持しながら探索する。
-- 起点パッケージからの下流依存(影響を受ける側)を列挙
WITH RECURSIVE deps(from_name, to_name, lvl, path) AS (
SELECT from_name, to_name, 1 AS lvl, '/'||from_name||'/'||to_name AS path
FROM v_pkg_deps
WHERE to_name = UPPER(:root_pkg) AND from_type='PACKAGE'
UNION ALL
SELECT d.from_name, d.to_name, r.lvl+1,
r.path||'/'||d.from_name
FROM v_pkg_deps d
JOIN deps r ON d.to_name = r.from_name
WHERE INSTR(r.path,'/'||d.from_name||'/') = 0
)
SELECT DISTINCT from_name AS affected_pkg, lvl
FROM deps
ORDER BY lvl, affected_pkg;
コンパイル順序の導出とINVALID収束の支援
再コンパイルは依存の矢印と逆順に当てるのが原則となる。上流が仕様を提供し下流がそれを参照するため、下流から順にコンパイルすれば未解決が減りやすい。簡易的なトポロジカルソートは再帰CTEに入次数の考え方を取り入れるか、一時表に依存エッジを書き出して段階的に入次数ゼロの集合を取り出すことで算出できる。
-- トポロジカル順序(下流→上流)に並べるための一例
WITH nodes AS (
SELECT DISTINCT from_name AS n FROM v_pkg_deps
UNION
SELECT DISTINCT to_name AS n FROM v_pkg_deps
),
edges AS (
SELECT DISTINCT from_name AS src, to_name AS dst FROM v_pkg_deps
),
ranked(n, rank) AS (
SELECT n, 0 FROM nodes n
WHERE NOT EXISTS (SELECT 1 FROM edges e WHERE e.src = n.n) -- 参照される側から見て下流
UNION ALL
SELECT e.dst, r.rank+1
FROM edges e JOIN ranked r ON e.src = r.n
)
SELECT n, MAX(rank) AS compile_order
FROM ranked
GROUP BY n
ORDER BY compile_order, n;
グラフ可視化のためのDOT生成と外部ツール連携
関係者に構造を共有するならGraphvizのDOT形式が扱いやすい。DB内でそのままDOTを出力すれば、CIのアーティファクトとしてPNGやSVG化まで自動の流れに乗せられる。次のPL/SQLは同一スキーマのパッケージ依存をDOT文字列に整形する。
DECLARE
v_dot CLOB := 'digraph deps { rankdir=LR; node [shape=box, style=rounded];' || CHR(10);
BEGIN
FOR r IN (
SELECT DISTINCT from_name, to_name
FROM v_pkg_deps
WHERE from_type='PACKAGE' AND to_type='PACKAGE'
) LOOP
v_dot := v_dot || '"'||r.from_name||'" -> "'||r.to_name||'";' || CHR(10);
END LOOP;
v_dot := v_dot || '}';
INSERT INTO pkg_dep_exports(id, dot, created_at)
VALUES (pkg_dep_seq.NEXTVAL, v_dot, SYSTIMESTAMP);
DBMS_OUTPUT.PUT_LINE(v_dot); -- 開発時は出力、運用では表へ蓄積
END;
/
-- 事前に格納先を用意
-- CREATE TABLE pkg_dep_exports(id NUMBER PRIMARY KEY, dot CLOB, created_at TIMESTAMP);
-- CREATE SEQUENCE pkg_dep_seq;
INVALIDの震源特定と再発防止の観点
ALL_OBJECTSと結合することで、現在INVALIDのノードとその上流を重ね合わせて原因位置を特定できる。仕様変更で連鎖INVALIDが起きた場合、まず仕様パッケージを上流から直し、その後に下流をまとめて再コンパイルすると収束が早い。次のクエリはINVALIDなパッケージと、それに直接依存する下流の一覧を示す。
-- INVALIDになっているパッケージの下流を確認
WITH inv AS (
SELECT object_name FROM user_objects
WHERE object_type IN ('PACKAGE','PACKAGE BODY') AND status='INVALID'
)
SELECT d.from_name AS dependent_pkg, d.to_name AS invalid_root
FROM v_pkg_deps d
JOIN inv i ON d.to_name = i.object_name
ORDER BY invalid_root, dependent_pkg;
Editionとの併用で安全に可視化と切替を両立する
EBR環境ではEditionごとにオブジェクト定義が分岐するため、セッションでALTER SESSION SET EDITIONを切り替えて同じレポートを採取すると、現行と新実装の依存差分を比較できる。差分によってはEditioning Viewやクロスエディショントリガが必要な箇所が浮かび上がる。以下はEditionを切り替えて同じ依存ビューを採取する最小例で、CIのジョブから連続実行して差分をファイルに出すのが実務的だ。
ALTER SESSION SET EDITION = v1;
CREATE TABLE deps_v1 AS SELECT * FROM v_pkg_deps;
ALTER SESSION SET EDITION = v2;
CREATE TABLE deps_v2 AS SELECT * FROM v_pkg_deps;
-- 差分の抽出(v2で新たに追加された依存)
SELECT d2.from_name, d2.to_name
FROM deps_v2 d2
LEFT JOIN deps_v1 d1 ON d1.from_name=d2.from_name AND d1.to_name=d2.to_name
WHERE d1.from_name IS NULL;
名前解決とシノニム越しの依存を正しく扱う工夫
シノニムやデータベースリンクを跨ぐ参照はALL_DEPENDENCIESの行に反映されるが、見落としやすいのがパブリックシノニムの暗黙参照である。参照名を完全修飾で統一するリファクタや、スキーマ境界を跨ぐ依存を別色で描くルールをDOT生成時に組み込むと分析が安定する。色分けは属性を追加するだけでよく、たとえば外部スキーマ参照を赤、同一スキーマを黒に描く。
-- 外部参照を色分けしてDOTを生成する一例
DECLARE
v_dot CLOB := 'digraph deps { rankdir=LR; node [shape=box, style=rounded];';
BEGIN
FOR r IN (
SELECT from_name, to_name,
CASE WHEN INSTR(to_name, '.') > 0 THEN 'red' ELSE 'black' END AS color
FROM v_pkg_deps
WHERE from_type='PACKAGE' AND to_type='PACKAGE'
) LOOP
v_dot := v_dot || CHR(10) || '"'||r.from_name||'" -> "'||r.to_name||'" [color='||r.color||'];';
END LOOP;
v_dot := v_dot || CHR(10) || '}';
DBMS_OUTPUT.PUT_LINE(v_dot);
END;
/
日次レポートと品質ゲートへの組み込み
CIの品質ゲートに「依存グラフの循環検出」「SPEC変更時の下流テストセット自動抽出」「INVALID残存ゼロ」を入れておくと、変更の安全性が一段と高まる。循環はCONNECT BYでFROMとTOが再遭遇するパスの有無を調べれば検出できる。次のSQLは単純な循環有無のチェックで、結果が出たらパイプラインを失敗させる設計にしておく。
-- 循環検出(簡易版)。パス上に起点が再出現したら検出
WITH RECURSIVE c(from_name, to_name, path) AS (
SELECT from_name, to_name, '/'||from_name||'/'||to_name||'/'
FROM v_pkg_deps
UNION ALL
SELECT d.from_name, c.to_name, c.path||d.from_name||'/'
FROM v_pkg_deps d
JOIN c ON d.to_name = c.from_name
WHERE INSTR(c.path, '/'||d.from_name||'/') = 0
)
SELECT DISTINCT to_name AS cycle_anchor
FROM c
WHERE INSTR(path, '/'||to_name||'/') > 1;
まとめ
依存の可視化は、影響範囲の見積もりとリリース後の安定運用を同時に強化する基盤である。ALL_DEPENDENCIESをパッケージ単位に正規化し、再帰問い合わせで下流への広がりを把握し、トポロジカル順で再コンパイルの順序を得ることで、仕様変更や不具合修正の作業計画が明確になる。DOTの自動生成をCIに組み込み、循環検出とINVALIDゼロを品質ゲートに据えれば、変化に強いPL/SQL基盤へ着実に近づく。Edition切替と併用して差分を継続可視化し、外部参照の色分けまで行えば、複雑なスキーマでも依存の迷路で迷うことはなくなる。