v0.4.2 の時点のガイド(ソース)
dbxray / cli
cli 読解ガイド
DBのカタログ(テーブル・カラム・制約・外部キー等のメタ情報)を読み、人が読むMarkdownのテーブル定義書・ER図と、機械が読むスキーマのスナップショット(JSON Lines)を出力するコマンドラインツールです。--checkを付けると、DBとコミット済みのスナップショットの差分を検知します。このガイドは、入出力と使っている技術の事前知識から始めて、実行の流れ、データの持ち方、中心のロジック、ソースごとの役割と繋がりまでを順に説明します。
- 約16.7k 行main配下のJava(空行・コメントを含む目安)
- 4層+2presentation・application・domain・infrastructure と層の外のconfig・shared
- 3 出力形式Markdown・スナップショット・参考情報(insights)
- 2 DBMSPostgreSQL・Oracle。SQLはmapper XMLに分離
mainブランチ(0bb6bdd。PR #114まで)を読んで作成し、その後に入った--preview(関数・プロシージャが利用しているテーブルの出力。Issue #134・PR #137)の分を各章に追記しています(9.9節にまとめ)。その後、DB単位のREADMEに載る出力対象の集計(DatabaseMetrics・ExportSink.writeSummary)、関数の個別定義のファイルが重なる場合の警告、テンプレートの共通部品の整理(RowRenderer・MermaidSupport.erDiagramCodeBlock等)を反映し、サンプルDBに足した履歴テーブルemployee_historyに合わせて出力例を採り直しています。コンソールの出力例は、ビルドしたjarを、docs/sample/postgres/ddl.sqlを流し込んだPostgreSQL 16に対して実行して採取したものです(長いパスは短く書き換えています)。Markdown・JSONの例は、コミット済みのベースラインdocs/sample/postgres/outputから引用しています。
先に掴んでおく6つの要点
- 層と依存の向き:
presentation → application → domain ← infrastructure。ルールはdomainに、DB・ファイル・YAML・JSONとの接点はinfrastructureに、全体の組み立てはエントリーポイントとconfigにあります(3章)。 - 取得は2つの粒度:軽い情報(テーブル一覧・関連・トリガー等)は対象範囲全体を一括で、重い情報(カラム・インデックス・制約)はスキーマ・チャンク単位で取得して、書いたら捨てます(6章)。
- テーブルキーで繋ぐ:カラム・関連・トリガー・付帯情報は、テーブルをオブジェクトで持たず「スキーマ名+テーブル名」(
TableKey)で指します。別々に取得した情報を後から1テーブル分に組み立てられるのはこのためです(8章)。
1事前知識:このツールの入力と出力
このツールは「DBのカタログ → 文書」の一方向の変換器です。文書に載せる内容の入力が2つ(DBのカタログとサイドカーYAML)、実行のしかたを決める入力が3つあり、出力は3つの形式とコンソールです。
--checkはスナップショットだけを一時ディレクトリに作り、出力先のsnapshot/と比べます。通常実行
- DBから取得し、3つの形式を出力先へ書く
- 生成するファイルだけを作成・上書きする。DBから消えたテーブルのファイルは残るため、
--rm-distで先に出力先を消せる - 終了コード:成功0、失敗2
--check
- 取得は通常実行と同じ。スナップショットだけを一時ディレクトリに作り、出力先の
snapshot/とオブジェクト単位で比べる - 出力先には書かず、Markdown・ER図も作らない
- 終了コード:差分なし0、差分あり1、失敗2。CIで「マイグレーション後の再生成・コミット忘れ」を検知する用途
--preview:固めている途中の機能は、付けたときだけ動く
精度や出力の形を、使った人の声を聞きながら固めている途中の機能は、--previewを付けて実行したときだけ動きます。今あるのはfunctionTableUsageの1つで、関数・プロシージャの定義本体から、利用しているテーブルと操作(C・R・U・D)を機械的に抽出します(9.9節)。結果は関数の定義書の「利用しているテーブル」と、参考情報のfunctionTableUsages.jsonに出ます。
- 出力・挙動は互換性の対象にしません(
CONTRIBUTING.mdの「互換性を約束する範囲」)。外すときは機能追加として扱います。 - スナップショットは変えません。そのため
--checkの結果には影響せず、--checkと同時に指定しても効果はありません(その旨を表示します。7章)。
出力先の構成
出力はすべてoutput.path(未指定なら./output)の下に、DBごとのディレクトリに分けて置かれます。同じ出力先へ複数のDBを出力しても混ざりません。
{output.path}/
├─ {DB名}/ Markdown(人が読む)
│ ├─ README.md 一覧へのリンク集と、出力対象の集計
│ ├─ tableList_{DB名}.md テーブル一覧(常に出力)
│ ├─ erDiagramList_{DB名}.md スキーマ別ER図の索引
│ ├─ erDiagram_{DB名}_{スキーマ}.md スキーマ別ER図(大きいと _group{n} に分割)
│ ├─ viewpointList_{DB名}.md・viewpoint_{DB名}_{id}.md 観点
│ ├─ triggerList / functionList / sequenceList / typeList_{DB名}.md
│ └─ {スキーマ}/
│ ├─ table | view | materialized_view/{テーブル}.md テーブル定義書
│ └─ function | sequence | type/{名前}.md 個別定義(関数は--previewで「利用しているテーブル」の節が付く)
├─ snapshot/{DB名}/ JSON Lines(機械が読む。--checkの比較対象)
│ ├─ database.json
│ └─ {スキーマ}/tables.jsonl・functions.jsonl・sequences.jsonl・types.jsonl
└─ insights/{DB名}/ 参考情報(--checkの対象外)
├─ viewpoints.json 観点(宣言した場合だけ)
└─ {スキーマ}/functionTableUsages.json 関数・プロシージャの利用しているテーブル(--previewを付けた場合だけ)
一覧は、テーブル一覧以外は対象が1件以上あるときだけ出力します。どのファイルをどのクラスが書くかは10章にまとめています。
同じ取得結果から3つの形式を作る
Markdownは人が読む最終成果物、スナップショットは機械が読む中間表現です。どちらもDBから1回取得した同じデータ(1テーブル分ならTableDefinitionContent)から作るため、内容は食い違いません。スナップショットは実行のたびに変わる生成日を含まないため、DBが変わらなければ何度実行しても同じ内容になり、--checkが日付で差分を出しません。観点はスキーマの事実ではなく「見せ方」なので、スナップショットには含めず、参考情報として別に出力します。関数・プロシージャが利用しているテーブルも、DBが持つ事実ではなく定義本体から抽出した参考値なので、同じく参考情報として出します。
サイドカーYAML:DBに無い情報を足す
Markdownを手で編集しても、次の生成で上書きされます。DBのコメントに書けない情報は、設定ファイルのannotationsで指定するYAMLに書きます。コード上はこれをサイドカーと呼び(パスはsidecarPath)、性質の違う3種類の情報を持ちます。
| YAMLのキー | 足すもの | モデル | 反映先 |
|---|---|---|---|
tables | 文章(テーブル説明・テーブル備考・カラム備考) | Annotations・TableAnnotation | 定義書の各セル。スナップショットのdescription・remarks |
relations | 構造(DBに外部キー制約が無い関連=論理リレーション) | ForeignKeyEntity(RelationType.LOGICAL) | 定義書の「論理リレーション情報」「被参照情報」、ER図の破線 |
viewpoints | 読む単位(業務ドメイン別のテーブルのまとまり=観点) | Viewpoints・Viewpoint | 観点ページ・観点一覧、定義書の「所属する観点」、insights |
# docs/sample/postgres/annotations.sample.yml から抜粋
tables:
sample.department: # スキーマ名.物理テーブル名
remarks: 組織改編時はdepartment_codeを変更せず、department_nameのみ更新すること
columns:
department_code: 部署コード。他システム連携時のキーとして使用
relations:
- table: sample.audit_log # 参照元(子)
columns: [record_id]
parentTable: sample.employee # 参照先(親)
parentColumns: [employee_id]
cardinality: 0..1対多
viewpoints:
- id: personnel # 観点ページのファイル名に使う
name: 人事管理
tables: # table= と同じ記法
- sample.employee*
- "!sample.employee_directory_view"
- sample.department
- sample.parking_spot
2事前知識:使っている技術
コードを読むときに前提になる技術は次のとおりです。どれも使い方は限られているので、この章で押さえる範囲を知っていれば読めます。
| 技術 | 何に使うか | 主な場所 |
|---|---|---|
| Java 21 | record・sealed interface・パターンマッチ・テキストブロック | コード全体 |
| MyBatis 3.5 | SQLをXMLに書き、結果をDTO(record)に詰める | resources/mapper・infrastructure.db |
| SnakeYAML 2.7 | サイドカーYAMLの読み込み | SidecarYamlRepository |
| Jackson 2 | スナップショット・参考情報のJSON化 | JacksonSnapshotSerializer |
| Mermaid | ER図の記法。GitHubがMarkdownの中で図に描く | ErDiagramTemplates・MermaidSupport |
| log4j2 | ログファイルと、警告のコンソール出力 | log4j2.xml |
| JUnit 6・ArchUnit・Testcontainers | 単体テスト・構造の規約の検査・Docker上の実DBでの結合テスト | modules/cli/src/test・modules/cli/src/integrationTest |
2.1 Java 21:record・sealed・パターンマッチ
このコードベースのクラスの多くはrecordです(エンティティ・DTO・スナップショット・request)。recordのコンパクトコンストラクタ(引数リストを書かないコンストラクタ)で、受け取ったリストの複製や値の正規化をしています。
public record NodeLimit(int value) {
public NodeLimit {
value = Math.max(value, 0); // 0以下は「上限なし」(0)へ揃える
}
}
「描くか省くか」のような場合分けは、sealed interfaceとswitchのパターンマッチで表します。switchは全パターンの網羅をコンパイラが検査するので、場合を増やすと分岐の書き漏れがコンパイルエラーになります。
public sealed interface RenderingPlan {
ForeignKeyGroup group();
record Draw(ForeignKeyGroup group) implements RenderingPlan {}
record Omit(ForeignKeyGroup group, NodeLimit limit) implements RenderingPlan {}
}
// ErDiagramWriter.writeErDiagramPage(要約)
final PagedSection<?> detail = switch (plan) {
case RenderingPlan.Omit omit -> /* 図の代わりに外部キー一覧を載せる */
case RenderingPlan.Draw draw -> /* 図に載せたテーブルの一覧を載せる */
};
| 書き方 | このリポジトリでの例 |
|---|---|
| recordのコンパクトコンストラクタ | TableDetail・TriggerEntityがリストをList.copyOfで複製する。Sidecar・TableAnnotationがnullを空へ揃える |
sealed interface と switch | RenderingPlan(Draw・Omit)、ForeignKeyGroups.PageComposition(Single・Grouped) |
| レコードパターン | ErDiagramTemplates.erDiagramのplan instanceof RenderingPlan.Omit(ForeignKeyGroup omitted, NodeLimit limit) |
テキストブロック(""") | Markdownの表のヘッダー(TableDefinitionTemplates等) |
| 振る舞いを持つenum | Cardinality(Mermaidの端点とラベル)、SnapshotKind.FUNCTION(識別名に引数を足す)、ExitStatus(終了コード) |
2.2 依存関係の組み立て:コンストラクタで受け取り、Componentsが2段階で組み立てる
DI(依存性の注入)は、クラスが使う部品を自分でnewせず、コンストラクタの引数で受け取る書き方です。このツールはDIコンテナ(Guice等)を使わず、誰に何を渡すかをComponents(エントリーポイントと同じパッケージ)がnewで書き下します。押さえる規則は3つです。
- アノテーションなし:コンストラクタに
@Inject等は付けない。ArchitectureTestがcom.google.inject・jakarta.injectへの依存を検出する。 - 具象クラスも
Componentsがnewする:コントローラー・2つのユースケース・SchemaExportPipeline・OutputDirectoryValidatorも同じ。コンストラクタの引数を変えたときの直し漏れは、コンパイルエラーになる。 - 2段階:出力先の検証をDBへ接続する前に行うため、DB種別に依存しない部品(
outputDirectoryValidator())と、接続して種別が分かってから組み立てる部品(controller(databaseType, sqlSessionFactory))に分ける。
CatalogRepositoryの実装は接続先がPostgreSQLかOracleかで変わるため、接続して種別が分かってから第2段(controller)で選びます。CatalogRepositoryに依存する3つ(パイプライン・ユースケース・コントローラー)は、このため第2段で組み立てます。// Dbxray.createController:DBへ接続するのはここだけ
final SqlSessionFactory sqlSessionFactory = MyBatisSqlSessionFactories.create(connectionSettings);
return components.controller(
DatabaseTypeDetector.detect(sqlSessionFactory), // ここで接続して種別を判定
sqlSessionFactory);
コンストラクタの引数の直し漏れ・CatalogRepositoryの実装の選び漏れ(switchがDatabaseTypeを網羅する)はコンパイルエラーになります。両方のDB種別でコントローラーまで組み立てられることは、DBへ接続しないComponentsTestが確かめます。
2.3 MyBatis:SQLはXML、結果はrecord
MyBatisは、SQLをXMLに書き、結果の列をJavaのオブジェクトへ詰めるライブラリです。このツールが発行するのはすべてSELECTで、更新はありません。
selectDatabaseだけは1行を返すselectOne)。DTOからエンティティへの変換で、値が無いことを空文字へ、カンマ区切りの列名をリストへ、という具合に値の形をドメインの表現へ揃えます(8.3節)。読むときに戸惑いやすい点が4つあります。
- SQLのIDは文字列で組み立てる。mapper XMLの名前空間
com.dbxray.domain.repository.postgresql.CatalogRepositoryはJavaのクラス名に見えますが、そういうインタフェースはありません。AbstractCatalogRepositoryが「名前空間+.+ID」を文字列で作ってsession.selectListに渡します。IDEで定義へ移動できないので、ID(selectTableInfo等)の文字列でmapper XMLを検索してください。 - 両方のmapperを常に読み込む。
mybatis-config.xmlはPostgreSQL用・Oracle用の両方を登録し、DatabaseTypeの名前(postgresql・oracle)で名前空間を選びます。 - 引数は
schemaList・tableListの2つだけ。XMLの<if>で、リストが空なら絞り込み自体を外します。そのため空のリストでselectColumnListを呼ぶと全テーブルのカラムを取ってしまうので、Java側で空を弾いています。 - 列とrecordは名前で対応する。SQLの別名(
AS "schemaName")とDTOのコンポーネント名を、mybatis-config.xmlのargNameBasedConstructorAutoMappingで対応させます。resultType="TableDto"はtypeAliasesで付けた短い名前です。
// AbstractCatalogRepository(要約)
baseSqlPath = "com.dbxray.domain.repository." + databaseType.id() + ".CatalogRepository.";
try (SqlSession session = sqlSessionFactory.openSession()) { // 呼ぶたびにプールから接続を借りる
return session.selectList(baseSqlPath + sqlId, Map.of("schemaList", schemaList, "tableList", tableList));
} catch (PersistenceException e) {
throw new RuntimeException("Failed to select: " + baseSqlPath + sqlId, e); // どのSQLかを添えて包む
}
| リポジトリのメソッド | SQLのID | DTO → エンティティ | 呼ぶ場所 |
|---|---|---|---|
selectDatabase | selectDatabaseInfo | DatabaseDto → DatabaseEntity(メジャーバージョンを含む) | fetchTargets |
selectTableList | selectTableInfo | TableDto → TableEntity(区分をenumへ) | fetchTargets |
selectForeignKeyList | selectForeignKeyInfo | ForeignKeyDto → ForeignKeyEntity(多重度を判定) | fetchTargets |
selectTriggerList | selectTriggerInfo | TriggerDto → TriggerEntity(イベントを分解、本体の末尾の空白を除く) | fetchTargets(target.objectsにtrigger) |
selectPartitionList | selectPartitionInfo | PartitionDto → PartitionEntity | fetchTargets |
selectViewReferenceList | selectViewReferenceInfo | ViewReferenceDto → ViewReferenceEntity(区分をenumへ) | fetchTargets(出力対象のビューの分へ絞る) |
selectFunctionList | selectFunctionInfo | FunctionDto → FunctionEntity(本体なし) | fetchTargets(function) |
selectSequenceList | selectSequenceInfo | SequenceDto → SequenceEntity | fetchTargets(sequence) |
selectTypeList | selectTypeInfo | TypeDto → TypeEntity | fetchTargets(type) |
selectColumnList | selectColumnInfo | ColumnDto → ColumnEntity | fetchDiagramBoxes(ER図の箱) |
selectFunctionDefList | selectFunctionDefInfo | FunctionDto → FunctionEntity(本体あり) | export(スキーマごと) |
selectTableDetails | selectColumnInfo・selectIndexInfo・selectConstraintInfo | 3種類をテーブルごとに振り分けてTableDetail | export(チャンクごと) |
実行したSQL(DEBUG)と取得した行(TRACE)は、MyBatisがmapperの名前空間(このツールのパッケージ配下)のロガーへ出します。既定のレベルはINFOなのでログには残らず、調べるときだけ-Ddbxray.log.level=debugで上げます(2.6節)。Oracle用のmapper(838行)は、LONG型の列(ビューのソース・トリガーの本体等)やDBのバージョン(DBMS_DB_VERSION)を読むためにSQLの中でPL/SQLの関数を定義する(WITH FUNCTION)など、PostgreSQL用(670行)より手が込んでいます。DBごとの違いはSQLで吸収し、同じDTOへ揃えます。
2.4 Mermaid の erDiagram
ER図は、Markdownの中に書くMermaid記法のテキストです。GitHubは```mermaidのコードブロックを図として描画します。サンプルのdepartmentの定義書に出力されたER図をそのまま示します。
```mermaid
erDiagram
sample_department["department"]
sample_employee["employee(従業員)"]
sample_department ||--o{ sample_employee : "employee_department_id_fkey"
sample_department {
integer department_id PK
character_varying department_code
character_varying department_name
}
sample_employee {
integer department_id FK "所属部署ID"
}
```
| 行 | 意味 | 作るメソッド |
|---|---|---|
sample_department["department"] | 識別子と表示名。識別子は「スキーマ_テーブル」で、記号は_に置き換える。表示名は「テーブル名(論理名)」 | MermaidSupport.mermaidId・aliasLine・assignLabels |
A ||--o{ B : "名前" | 関連線。親→子の向き。両端の記号が多重度、--が実線(物理の外部キー)、..が破線(論理リレーション) | MermaidSupport.relationLine・Cardinality.notation |
B { 型 名前 PK, FK "論理名" } | 箱に表示するカラム。型は桁数を除き、記号を_にする | MermaidSupport.attributeBlock・sanitizeType |
Mermaidのclick構文はGitHubで無効になるため、図からテーブル定義書へは、図の下に置く「ER図に掲載しているテーブル」の表のリンクで辿ります。
2.5 JSON Lines と Jackson
スナップショットは、1行に1オブジェクトのJSONを並べたJSON Lines(.jsonl)です。1テーブルが1行なので、gitの差分がそのままオブジェクト単位の差分になり、--checkも行をオブジェクトとして突き合わせられます。JSONへの変換はJacksonSnapshotSerializerが行い、次の性質に依存しています。
- 項目の順序が固定:recordのコンポーネントの宣言順に出力される。同じ内容からは常に同じ文字列になる(差分検知の前提)。
- 空は出力しない:
JsonInclude.Include.NON_EMPTYで、null・空文字・空リストの項目を省く。真偽値のfalseは省かない。 - 日本語はそのまま:非ASCII文字をエスケープしない(git diffで読めるように)。
// snapshot/testdb/database.json
{"formatVersion":1,"name":"testdb","dbms":"PostgreSQL","majorVersion":16}
DBの版はメジャーバージョンだけを持ちます(DatabaseEntity.majorVersion)。マイナー版を更新したときに、--checkが差分を報告しないようにするためです。
domain層はJacksonに依存しません。変換のインタフェースSnapshotSerializerをdomainに置き、Jacksonを使う実装をinfrastructureに置いています。
2.6 log4j2 と ArchUnit
- log4j2(
log4j2.xml):ログを、実行したディレクトリからの相対のvar/log/dbxray.logへ書く。既定のレベルは、このツール(com.dbxray)がINFO、ライブラリがWARN。INFOには、接続先のDBMS・ドライバの版、件数、処理時間のような、1回の実行で数件に収まる調査の起点だけを出す。MyBatisは実行したSQL(DEBUG)と取得した行(TRACE。DBのメタ情報そのもの)をこのツールのパッケージ配下のロガーへ出すため、既定では残さず、調べるときに-Ddbxray.log.level=debug(またはtrace)で上げる。このツールのWARNだけは、標準エラーへも[warn]:付きで出す。処理結果の[result]:はロガーを通さずSystem.outへ直接出す。 - ログに出さないもの:パスワード、接続URL、CLI引数・設定値の値、設定ファイル・サイドカーYAMLの内容を引用するライブラリの例外メッセージ。メッセージの改行は
%enc{%m}{CRLF}で\n・\rへ置き換え、DB由来の文字列(関数の定義等)で偽のログ行を作らせない。 - ArchUnit(
ArchitectureTest):レイヤーの依存方向や置き場所などの規約を、単体テストとして検査する。違反するとビルドが失敗する。規約の一覧は3章。
サンプルDBに対して通常実行したときの、既定のレベルのログファイルの全体です(日時とスレッド名を省き、長いパスを短くしています)。
INFO MyBatisSqlSessionFactories Initializing SqlSessionFactory. [config=mybatis-config.xml]
INFO MyBatisSqlSessionFactories Initialized SqlSessionFactory.
INFO DatabaseTypeDetector Connected to the database. [product=PostgreSQL 16.15 (…), driver=PostgreSQL JDBC Driver 42.7.13]
INFO ExportSchemaController [START] exportSchema
INFO SidecarYamlRepository Loaded sidecar. [annotations=…/annotations.sample.yml, tableCount=2, relationCount=1, viewpointCount=3]
INFO SchemaExportPipeline Skipping orphan table annotation check because the output target is filtered.
INFO SchemaExportPipeline Merged logical relations declared in the sidecar. [relationCount=1]
INFO SchemaExportPipeline Exported schema objects. [tables=15, functions=11, sequences=9, types=3, triggers=5]
INFO ExportSchemaController [ END ] exportSchema [elapsedMillis=1395]
メッセージは英語で要旨を先に書き、値は末尾の[key=value, …]にまとめる、というのがこのリポジトリのログの書式です(docs/development/coding-guidelines.mdの「ログ」)。
3全体像と読む順
com.dbxrayの下は、4つの層と、層の外の2つ(config・shared)に分かれます。依存の向きはpresentation → application → domain ← infrastructureで、domainは他のどの層にも依存しません。DB・ファイル・JSONとやり取りするインタフェースをdomainに置き、その実装をinfrastructureに置く(依存関係逆転)ことで、外部に触れるコードをドメインの外へ出しています。
OutputPathResolver・SnapshotSerializer)を実装します。エントリーポイントは全体を組み立てる役割(Componentsがinfrastructureの実装をnewする)のため全層に依存でき、configに依存してよいのはエントリーポイントとinfrastructure(ConnectionSettingsがInvalidConfigurationExceptionを使う)だけです。| 層・パッケージ | ひとことで |
|---|---|
| エントリーポイント(直下) | main。CLI引数・設定・出力先の検証と、依存関係の組み立て(Components)。--help・--versionの表示 |
presentation(.dto・.type) | ユースケースを呼んで結果を文面にする。失敗の報告、終了コード |
application | 2つのユースケースと、両者が共有する取得・書き出しの段取り |
domain.model(12パッケージ) | エンティティ・集合・値オブジェクトと、それらが持つルール |
domain.repository | DB・サイドカーYAML・ファイルとのやり取りのインタフェース |
domain.service(writer以外) | 出力形式ごとのSink、突き合わせ、配置の規則(DB由来の名前を安全なパスにする)、スナップショットの書き込み・比較、関数の定義本体から利用しているテーブルを抽出するtableusage(9.9節) |
domain.service.writer | Markdownの段取り(Writer)と組み立て(Templates) |
infrastructure | MyBatis・DTO・接続、ファイル、サイドカーYAML、パス、Jackson |
config | 設定ファイルの読み込み(YAMLの構文誤りの文言を含む) |
shared.exception | 利用者が直せる誤りを表す例外 |
構造の規約(ArchitectureTestが検査するもの)
次の規約はArchUnitの単体テストで検査され、破るとビルドが失敗します。違反したら、テストを緩めずに依存や置き場所のほうを直す、というのがこのリポジトリの方針です(docs/development/coding-guidelines.md)。
| 規約 | 内容 |
|---|---|
| レイヤーの依存方向 | presentationを使えるのはエントリーポイントだけ。applicationはエントリーポイントとpresentationだけ。infrastructureはエントリーポイントとconfigだけ。configはエントリーポイントとinfrastructureだけ |
domain.modelの循環 | 概念ごとのパッケージ同士が循環して依存しない(向きはdomain-model.mdの図) |
shared | 例外だけを置き、JDK以外に依存しない |
| ファイルI/O | application・domainはjava.nio.file.Filesを直接使わない(FileRepositoryを通す) |
| 出力先のパス | Path.resolve・resolveSiblingを呼ぶのはinfrastructure.pathだけ(DB由来の名前を含むパスを、出力先の外を指さないことを確かめるOutputPathResolverの実装でだけ組み立てる。10章) |
| 現在日時 | 引数なしのnow()を呼ばない(DIで受け取るClockから求める。テストで日付を固定するため) |
| DIコンテナ | Guice・jakarta.injectに依存しない(@Injectは付けず、Componentsがnewで組み立てる) |
| 接尾辞と置き場所 | …Entity→domain.model、…Dto→infrastructure.db.repository.dtoかpresentation.dto、…Templates→domain.service.writer、…Writer→domain.service、…Locations→domain.service.path、…Usecase・…Request→application |
| リポジトリ | domain.repositoryには…Repositoryのインタフェースだけ。実装はinfrastructure..repository |
| 定義本体を読むパッケージ | domain.service.tableusage・domain.model.tableusageは正規表現を使わない(java.util.regexと、String.matches・replaceAll・replaceFirst・split・splitWithDelimitersと、java.util.Scanner)。定義本体はDBでCREATEの権限を持つ人が書ける入力で、正規表現はバックトラックで入力の長さに比例しない時間がかかり得る(ReDoS)ため、1文字ずつ読む(9.9節) |
クラス名の接尾辞が表す役割
| 接尾辞 | 役割 | 例 |
|---|---|---|
…Entity | DBから取得したメタ情報1件のrecord | TableEntity・ColumnEntity |
| 複数形 | エンティティの集合。検索・振り分けのメソッドを持つ(ファーストクラスコレクション) | Tables・ForeignKeys・Triggers |
…Dto | 層の境界で受け渡すだけの形(SQLの結果・コントローラーの結果) | TableDto・ResultDto |
…Repository | 外部とのやり取り。インタフェースはdomain、実装はinfrastructure | CatalogRepository |
…Usecase・…Request | ユースケースと、その入力をまとめたrecord | ExportSchemaUsecase |
…SinkFactory | 出力形式ごとのExportSinkを、出力先を決めて作る | MarkdownExportSinkFactory |
…Writer | 何を・どの順で・どのファイルに書くかの段取り | ErDiagramWriter・SchemaSnapshotWriter |
…Templates | Markdownの行・セクションの組み立て(副作用の無いstaticメソッド) | TableDefinitionTemplates |
…Locations | 出力物の配置・ファイル名の規則 | DocumentLocations・SnapshotLocations |
…Snapshot | スナップショットの1行(JSONになるrecord) | TableSnapshot |
クラス名の本体(何についてのクラスか)は、利用者向けのドキュメントの呼び方に揃えた用語集(domain-model.md)に従います。ただしForeignKeyEntity・ForeignKeysは歴史的経緯による名前で、外部キーだけでなく論理リレーションも含む「関連」を表します。
おすすめの読む順
上から順に読むと、外側(起動と入口)から内側(ルール)へ進めます。
Dbxray:main→run→Components.create→createController。処理全体の骨格です(4章)。ExportSchemaControllerと2つのユースケース:2つのモードの違いが「どのSinkを渡すか」だけだと分かります。SchemaExportPipeline:取得の順序と、スキーマ・チャンク単位の分割。6章の図と合わせて読むと速いです。ExportSinkと3つの…SinkFactory:出力形式ごとに「いつ何を書くか」(6章の表)。domain.model.target:ExportTargets・TableDefinitionContent・TableScope。取得したデータを組み立てる単位です(8章)。domain.model.table・relation:TableKey・ForeignKeys・ForeignKeyGroups・Cardinality。関連とER図のルールです(9章)。- 関数が利用しているテーブルの抽出(
--preview):FunctionTableUsageAnalyzer→FunctionBodyLocator→TableReferenceScanner→SqlLexerの順に、9.9節の図と合わせて読みます。 - 気になる出力のWriter→Templates:たとえば
TableDefinitionWriter(セクションの順)→TableDefinitionTemplates(各セクションの組み立て)。 - infrastructure:
AbstractCatalogRepositoryとmapper XML、SidecarYamlRepository。 - 入口の検証:
CliArguments・DbxrayProperties・ConnectionSettings・FailureReporter(5章)。
4起動から終了までの流れ
Dbxray.mainは、処理全体を1つのtry-catchで囲み、次の順に進みます。前半(1〜6)はDBへ接続せずに入力を検証し、後半(7〜11)で接続して出力します。入力の誤りを接続より前に報告するのは、DBに接続できない環境(接続情報の誤り・DBの停止中)でも、設定の誤りに先に気付けるようにするためです。
mainの1箇所だけで、どの段階の失敗も右端を通ってそこへ集まります。CliArguments.parseは未知の引数があっても例外を投げず、モード(--checkか)を決めてから2で誤りにします。そのため--chekと書き誤ると、通常実行の開始メッセージの後に失敗します。// Dbxray.run(通常実行。runCheckも同じ形)
System.out.println("Starting output of database documents. …");
cliArguments.requireKnownArguments();
final ConfigFile configFile = ConfigFile.load(cliArguments.configPath()); // 設定ファイルは1回だけ読む
final ExportSchemaRequest request =
DbxrayProperties.of(configFile, cliArguments.settingOverrides())
.toExportSchemaRequest(cliArguments.isRmDist(), cliArguments.isPreview());
final Components components = Components.create();
components.outputDirectoryValidator().validate(request);
final ConnectionSettings connectionSettings = connectionSettings(configFile, cliArguments); // ConnectionSettings.merge
final ResultDto resultDto = createController(components, connectionSettings).execute(request);
System.out.println(resultDto.resultMessage());
return ExitStatus.SUCCESS;
入口で文字列を型に変える
設定値は生の文字列のまま深い層へ渡しません。DbxrayPropertiesが空白を除き、整数へ変換し、出力対象の条件をTargetSelection.ofで型(TableScope・OutputObjectTypeの集合)へ変換します。誤りは1件ずつではなく、見つかった分をまとめて報告します。結果はrequestのrecordになり、エントリーポイント→コントローラー→ユースケースの3層を分解・再構築されずに通ります。
| record | 持つもの | 使う場所 |
|---|---|---|
ExportSchemaRequest | targetSelection・sidecarPath・outputPath・chunkSize・erDiagramMaxNodes・erDiagramDistance・rmDist・previewFeatures(有効にするプレビューの機能の集合) | 通常実行 |
CheckDocumentDiffRequest | 上からerDiagramMaxNodes・erDiagramDistance・rmDist・previewFeaturesを除いたもの(Markdownを描かず、出力先も消さず、プレビューの機能も使わないため) | --check |
TargetSelection | TableScope(スキーマ名+テーブル名パターン)とSet<OutputObjectType> | 両方のrequestの中 |
--previewは機能の集合に変える:--check・--rm-distと同じ値を持たないフラグで、設定ファイルの項目にはありません。CliArguments.isPreviewが真なら、DbxrayProperties.toExportSchemaRequestがPreviewFeatureのすべてをpreviewFeaturesに入れます(機能を個別に選ぶ引数はありません)。--checkではrunCheckが「効果が無い」旨を表示するだけで、CheckDocumentDiffRequestは持ちません(7章)。- 設定ファイルの探し方:
ConfigFileは、--configで指定したファイル、無ければ実行したディレクトリのconf/config.ymlを読みます。jarの場所ではなくカレントディレクトリが基準で、配布用のrun.sh・run.batは自分のディレクトリへ移動してから起動します。全項目をCLI引数で上書きする場合でも、設定ファイル自体は必要です(実行するディレクトリを誤ったときに、既定の出力先へ黙って出力しないため)。 - 設定項目の仕様は1つの表に持つ:
DbxrayProperties.SETTINGSが、項目ごとに設定ファイルでの位置(output.path等)・上書きするCLI引数名(--output-path等)・値の形式(1つの値・整数・リスト)を持ちます。CliArgumentsはこの表から上書きの引数を引くため、設定項目を足せば上書きの引数も増えます。 - 接続情報:
ConnectionSettingsは設定ファイルのdatabaseに、環境変数DBXRAY_DB_PASSWORDのパスワード、CLI引数の順で値を重ねて検証します。ファイルにpasswordを書くと誤りです(作業ディレクトリのファイルはAIエージェント等のツールから読まれ得るため、秘密をファイルに残さない)。urlにパスワードを埋め込む書き方(?password=・sslpassword=・Oracleのuser/pass@)も、同じ理由で誤りにします。
5失敗の扱いと終了コード
失敗は3種類に分けて扱います。どれに当たるかで、表し方と利用者への報告が変わります。
| 種類 | 例 | 表し方 | 報告 |
|---|---|---|---|
| 利用者が直せる誤り | 設定・引数の誤り、出力先が危険、DBに接続できない、非対応のDB、サイドカーYAMLが無い・読めない | UserCorrectableException(設定ファイルの誤りは派生のInvalidConfigurationException)。何を直せばよいかをメッセージに書く | [result]:FAIL+メッセージ。ログにスタックトレースを残さない |
| 想定外の失敗 | I/O・SQLの失敗、不具合、JVMのエラー | 非検査例外のまま伝える | [result]:FAIL+メッセージ+ログの場所。ログにスタックトレース |
| 業務上の結果 | --checkの差分、孤児付帯情報、除外した関連 | 例外にせず値で返す(DiffResult・ConsistencyNotice) | 差分の報告、警告のログ([warn]:) |
捕まえるのは main の1箇所だけ
コントローラー・ユースケースは例外を捕まえません。mainがThrowableまで捕まえてFailureReporterに渡し、終了コード2で終わります(Errorまで捕まえるのは、捕まえないとJVMが終了コード1で終わり、--checkの「差分あり」と区別できなくなるため)。途中の層でcatchしてよいのは、次の3つの場合だけです。
| 目的 | 実例 |
|---|---|
| 検査例外を非検査例外で包む | LocalFileRepository(IOException→UncheckedIOException)、AbstractCatalogRepository.select(どのSQLかを添えてRuntimeExceptionで包む) |
| 下位の例外を利用者が直せる誤りへ置き換える | DatabaseTypeDetector(接続の失敗)、SidecarYamlRepository(YAMLの構文誤り)、DbxrayProperties(TargetSelectionのIllegalArgumentException)、ConfigFile(YAMLの構文誤り。文言はYamlSyntaxErrors) |
| フォールバックする | SidecarYamlRepository(誤った観点を、警告して読み飛ばす) |
包むときは原因(cause)を渡し、tryの範囲は置き換えたい呼び出しだけに絞ります。例外はYAMLの構文誤りで、SnakeYAMLの例外のメッセージは誤りの行をそのまま引用するため(設定ファイルに誤って書いたパスワードが画面とログに出得る)、YamlSyntaxErrorsで種類と位置だけの文言にし、原因は渡しません。UserCorrectableExceptionを投げるのは、入口(CLI引数・設定・出力先の検証)と、利用者の入力・実行環境に触れるインフラ(DBへの接続・サイドカーYAMLの読み込み)だけで、domain・applicationでは投げません。
FailureReporter の報告の形
[errmsg]に例外のメッセージを、[cause]に原因の連鎖のうち表示に含まれていないものを浅い順に並べます。原因のメッセージを繰り返しているだけの例外(MyBatisの例外等)は省くため、包んだ箇所でDBが返したエラーが失われず、かつ重複もしません。以下は実際の出力です。
$ java -jar dbxray.jar --chek
Starting output of database documents.
Please wait a moment ...
[result]:FAIL
Failed to output database documents.
[errmsg]:Unknown argument: --chek (values are not shown; specify a value as --name=value) (available arguments: --check, --rm-dist, --preview, --help, --version, --config=<path>, --db-driver=<value>, …, --annotation-path=<value>)
$ java -jar … --chunk-size=abc --output-objects=trigger,view --table='sample.,!'
[result]:FAIL
Failed to output database documents.
[errmsg]:Invalid configuration in conf/config.yml (overridden by --table, --output-objects, --chunk-size).
- output.chunkSize must be an integer: abc
- Invalid table pattern: sample., ! (the table name and the schema name must not be empty)
- Unknown output object type: view
$ java -jar … --db-url=jdbc:postgresql://localhost:5999/testdb …(DBが待ち受けていないポート)
[result]:FAIL
Failed to output database documents.
[errmsg]:Could not connect to the database. Check the connection settings (database in the configuration file, --db-* arguments and the DBXRAY_DB_PASSWORD environment variable) and that the database is running.
[cause]:org.postgresql.util.PSQLException: Connection to localhost:5999 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
[cause]:java.net.ConnectException: Connection refused
1つ目の(values are not shown; …)は、解釈できない引数の値を伏せている印です(--db-pasword=…のような書き誤りで、パスワードが画面とログファイルに残らないように)。2つ目は、3つの誤りがまとめて1回で報告されている例です。(overridden by …)は、誤った値がCLI引数から来ている可能性を示します。想定外の失敗では、最後にAn unexpected error occurred. See the log file for details. [logFile=./var/log/dbxray.log]が付きます。
警告:処理は続けるが知らせる
サイドカーYAMLの個々の記述の誤りや、DBと付帯情報の乖離は、読み飛ばして警告します(付帯情報の一部の書き誤りで、定義書全体の再生成を止めないため)。警告はWARNのログで、log4j2.xmlがログファイルに加えて標準エラーへ[warn]:付きで出します。以下は、誤りを含むサイドカーYAMLで実行したときの標準エラーです(終了コードは0)。
[warn]:Ignoring unknown key in 'tables' entry of sample.department. [key=descripton, annotations=warn.yml]
[warn]:Ignoring unknown 'cardinality' and falling back to the default. [entry=relations #1, cardinality=多対多, default=1対多, annotations=warn.yml]
[warn]:Ignoring viewpoint. Invalid viewpoint id: '人事' (use only letters, digits, '-' and '_', because it is used as a file name) [entry=viewpoints #1, id=人事, annotations=warn.yml]
[warn]:Annotation exists for a table that was not found (renamed or dropped?). [table=sample.employe]
[warn]:Column annotation exists for a column that was not found (renamed or dropped?). [table=sample.department, column=department_cd]
上の3行はYAMLを読むSidecarYamlRepositoryが直接ログへ出したもの(entry=relations #1は何件目の定義かを示す)、下の2行はExportTargetConsistencyが通知(ConsistencyNotice)として値で返し、SchemaExportPipeline.reportが重要度に応じてログへ出したものです。判定をするドメインサービスは、結果をどこへ出すかを知りません。
出力そのものの食い違いも警告します。関数のオーバーロードの個別定義は{関数名}_{番号}.mdになるため、番号を付けた名前が別の関数の名前と重なる(calcのオーバーロードと関数calc_1)と、後に書いたほうだけが残り、関数一覧のリンクもすべて同じファイルを指します。ExportSchemaUsecaseは書き出しの前にDocumentLocations.functionsSharingDefinitionFileで重なりを求め、Function documents share the same file name, so only the one written last is kept. [file=…, functions=…]を出します(ファイル名は互換性を約束する範囲のため、区切りを変えて避けることはしていません)。
孤児カラム備考の警告は、サイドカーYAMLに書いた順に出ます(TableAnnotationがカラム備考を記述順のまま持ち、orphanColumnNamesは順序のあるListで返す)。同じ入力なら毎回同じ順序になります。
終了コード
| コード | ExitStatus | 意味 |
|---|---|---|
| 0 | SUCCESS | 成功(--checkで差分なしを含む) |
| 1 | DIFFERENCE_FOUND | --checkで差分あり |
| 2 | FAILURE | 失敗。仕様上は「2以上」と範囲で定めていて、失敗の種類を分けるときは3以上を足す(POSIXのdiff・cmpと同じ考え方) |
--checkで差分があっても、表示の1行目は[result]:SUCCESSです。比較そのものは成功したという意味で、差分の有無は終了コードで判定します。JVMが起動できない場合などmainに入る前の失敗は、Javaの仕様で終了コード1になります。
6取得と書き出しの段取り(SchemaExportPipeline)
通常実行のユースケースは短く、取得と書き出しの段取りをSchemaExportPipelineに任せています。--checkのユースケースも同じパイプラインを使い、違いは渡すExportSinkのリストだけです。
// ExportSchemaUsecase.exportSchema
final Path outputBaseDir = outputPathResolver.resolveOutputBaseDir(request.outputPath());
final ExportTargets targets =
schemaExportPipeline.fetchTargets(request.targetSelection(), request.sidecarPath()); // 一括取得
final DiagramBoxes diagramBoxes =
schemaExportPipeline.fetchDiagramBoxes(targets, request.chunkSize()); // ER図の箱
warnFunctionsSharingDefinitionFile(targets.functions()); // 個別定義のファイルが重なる関数を警告(5章)
if (request.rmDist()) {
removeOutputBaseDir(outputBaseDir); // 取得に成功してから消す
}
schemaExportPipeline.export(
targets,
List.of(
markdownSinkFactory.create(
outputBaseDir,
NodeLimit.of(request.erDiagramMaxNodes()),
request.erDiagramDistance(), // テーブル定義書のER図の描画距離(9.7節)
diagramBoxes,
targets.foreignKeys()),
snapshotSinkFactory.create(outputBaseDir),
insightSinkFactory.create(outputBaseDir)),
request.chunkSize(),
request.previewFeatures()); // --previewで有効にした機能。空なら利用テーブルの抽出をしない(9.9節)
軽い情報は一括で、重い情報はチャンクで
対象スキーマ全体を一度にメモリへ載せないよう、取得を2つの粒度に分けています。テーブル一覧・関連・トリガー等は1件が軽く、ER図や被参照情報でスキーマ・チャンクをまたぐ参照を解決するのに全件が要るため、最初に一括で取得して最後まで持ちます。カラム・インデックス・制約はテーブル数に比例して重くなるため、スキーマごと・chunkSize件ごとに取得し、書き出したら捨てます。
DiagramBoxes)だけで描けるので、詳細をチャンク単位で取得する前のwriteOverviewで書き出せます。chunkSizeを小さくするとメモリは減り、DBへの問い合わせは増えます。出力の内容は変わりません(結合テストがchunkSizeを変えて同じ出力になることを確かめています)。READMEの集計はカラムの数を含むため、全テーブルを書き出した後のwriteSummaryで書きます。サンプルDB(ビューを含む15テーブル、schema=sample)で通常実行したときに発行されたSQLの順序です(-Ddbxray.log.level=debugで実行したログファイルから抜き出したもの)。
| 順 | 段階 | SQLのID | 行数 |
|---|---|---|---|
| — | fetchTargets | (サイドカーYAMLを読む。SQLではない) | — |
| 1 | selectDatabaseInfo | 1 | |
| 2 | selectTableInfo | 15 | |
| 3 | selectForeignKeyInfo | 10 | |
| 4 | selectTriggerInfo | 5 | |
| 5 | selectPartitionInfo | 7 | |
| 6 | selectViewReferenceInfo | 7 | |
| 7 | selectFunctionInfo | 11 | |
| 8 | selectSequenceInfo | 9 | |
| 9 | selectTypeInfo | 3 | |
| 10 | fetchDiagramBoxes | selectColumnInfo(関連を持つ12テーブル分) | 63 |
| 11 | export:関数(スキーマごと) | selectFunctionDefInfo | 11 |
| 12 | export:テーブル(チャンクごと) | selectColumnInfo | 75 |
| 13 | selectIndexInfo | 20 | |
| 14 | selectConstraintInfo | 29 |
15テーブルが既定のchunkSize(3000)に収まるため、12〜14は1回ずつです。chunkSize=1なら12〜14がテーブルの数だけ繰り返されます。--checkでは10(ER図の箱)を発行しません。6の7行は、サンプルの3つのビューが参照するテーブルの数の合計です(9.8節)。
出力形式ごとのSink
ExportSinkは「取得した情報を1つの出力形式で書き出す」インタフェースで、5つのメソッドを持ちます。パイプラインは取得した順にこれを呼ぶだけで、何をどのファイルへ書くかは実装(…SinkFactoryの内部クラス)が決めます。出力先は、Sinkを作るときに渡します。
| 呼ばれる時点 | Markdown | スナップショット | 参考情報 |
|---|---|---|---|
writeOverview(targets)一括取得の後に1回 | テーブル一覧・ER図と索引・トリガー/関数/シーケンス/型の一覧・観点ページと観点一覧・シーケンス/型の個別定義 | database.json・sequences.jsonl・types.jsonl | viewpoints.json(観点があれば) |
writeFunctionDefinitionsスキーマごと | 関数・プロシージャの個別定義(--previewなら「利用しているテーブル」の節つき) | functions.jsonl(利用しているテーブルは含めない) | {スキーマ}/functionTableUsages.json(抽出した場合だけ。抽出していない実行は前回のファイルを削除) |
beginSchemaTablesスキーマごと(既定は空の実装) | 何もしない | tables.jsonlを空にする(前回の内容へ追記しないため) | 何もしない |
writeTableDefinition(content)テーブルごと | テーブル定義書 | tables.jsonlへ1行追記 | 何もしない |
writeSummary(targets, metrics)全テーブルの後に1回(既定は空の実装) | README(出力する一覧へのリンクと、出力対象の集計) | 何もしない | 何もしない |
| 使うモード | 通常実行 | 通常実行・--check(一時ディレクトリへ) | 通常実行 |
writeFunctionDefinitionsが受け取るのは、関数・プロシージャ1つ分の出力内容FunctionDefinitionContent(関数+利用しているテーブル)のリストです。パイプラインがスキーマごとに、--previewのときだけFunctionTableUsageAnalyzerで抽出し、そうでなければ「抽出していない」印(FunctionTableUsage.notAnalyzed())を持たせます。Sinkは抽出するかどうかを知らず、Markdownは「利用しているテーブル」の節を出すか、参考情報はファイルを書くか消すかを、この印で決めます(9.9節)。
出力形式を足すときに触る場所
ExportSinkを実装したクラスとそのFactoryを作り、FactoryをComponentsで組み立てて、ユースケースが渡すリストに加えます。SchemaExportPipelineは出力形式を知らないので変えません。--checkの比較対象にしたくない出力は、CheckDocumentDiffUsecaseのリストに加えないだけで、比較から外れます(参考情報がこの方法で外れています)。
突き合わせの通知はログへ
取得の途中で、出力対象のテーブルと、関連・付帯情報・観点をExportTargetConsistency(ドメインサービス)が突き合わせ、気付くべき事柄をConsistencyNoticeの値で返します。パイプラインのreportが重要度に応じて、INFOはログファイルだけへ、WARNはログファイルと標準エラーへ出します。通知の種類は9.2節にまとめています。
7差分検知(--check)
--checkは「DBの今の状態から作ったスナップショット」と「出力先にコミット済みのスナップショット」を比べます。取得は通常実行と同じパイプラインで、Sinkをスナップショットの1つに減らし、書き込み先を一時ディレクトリにするだけです。
// CheckDocumentDiffUsecase.checkDocumentDiff
final Path committedDir = outputPathResolver.resolveOutputBaseDir(request.outputPath());
final Path generatedDir = fileRepository.createTempDirectory(CHECK_TEMP_DIR_PREFIX);
try {
final ExportTargets targets =
schemaExportPipeline.fetchTargets(request.targetSelection(), request.sidecarPath());
schemaExportPipeline.export(
targets, List.of(snapshotSinkFactory.create(generatedDir)), request.chunkSize(),
Set.of()); // プレビューの機能はスナップショットを変えないため、差分検知では有効にしない
return snapshotDiff.compare(
outputPathResolver.resolveSnapshotDirectory(generatedDir),
outputPathResolver.resolveSnapshotDirectory(committedDir));
} finally {
fileRepository.deleteDirectory(generatedDir);
}
snapshot/の下だけなので、兄弟のinsights/やMarkdownは構造的に比較から外れます。行をオブジェクトとして突き合わせる
SnapshotDiffは両方のディレクトリのファイルを読み、1行ごとに「どのオブジェクトの行か」の表示名を付けて突き合わせます。表示名はSnapshotKind.identifyが作り、同じ表示名でも別のファイル(別のDB)なら別物として扱います(内部のrecord Targetはファイルと表示名の組)。
| ファイル | 突き合わせの単位 | 表示名の例 |
|---|---|---|
{スキーマ}/tables.jsonl | 1行=1テーブル(ビューを含む) | table sample.employee |
{スキーマ}/functions.jsonl | 1行=1関数・プロシージャ。同名は引数で区別 | function sample.calculate_bonus(p_salary numeric) |
{スキーマ}/sequences.jsonl・types.jsonl | 1行=1オブジェクト | sequence sample.invoice_no_seq |
上記以外(database.json) | ファイル全体 | testdb/database.json |
内容が違うものは、両側の行をSnapshotSerializer.formatForDiffで「1項目1行・配列は1要素1行」に整形してから、UnifiedDiffGenerator(Myers法の自前実装。前後3行の文脈)でunified diffにします。1行のJSONのままだと、差分が「丸ごと削除+丸ごと追加」にしかならないためです。整形後の行番号は、ファイル上の行番号とは対応しません。
サンプルDBにsample.employee.nicknameカラムとsample.tmp_new_tableテーブルを足してから--checkした実際の出力です(終了コード1)。
[result]:SUCCESS
Difference detected between the generated document and the committed document.
Only in generated document (possibly missing commit):
- table sample.tmp_new_table
Content differs:
- table sample.employee
--- committed/testdb/sample/tables.jsonl (table sample.employee)
+++ generated/testdb/sample/tables.jsonl (table sample.employee)
@@ -15,6 +15,7 @@
{"name":"profile","logicalName":"プロフィール(JSON)","type":"jsonb","primaryKey":false,"notNull":true,"defaultValue":"'{}'::jsonb"}
{"name":"hired_date","logicalName":"入社日","type":"date","primaryKey":false,"notNull":true,"defaultValue":"CURRENT_DATE"}
{"name":"updated_at","logicalName":"更新日時","type":"timestamp without time zone","primaryKey":false,"notNull":true,"defaultValue":"now()"}
+ {"name":"nickname","type":"text","primaryKey":false,"notNull":false}
]
"indexes": [
{"name":"employee_employee_code_key","method":"btree","unique":true,"primary":false,"definition":"CREATE UNIQUE INDEX employee_employee_code_key ON sample.employee USING btree (employee_code)"}
足したカラムにはコメントが無いためlogicalNameの項目がありません(空は出力しない、2.5節)。
検知しないもの
- Markdownだけの差分:手作業の編集・削除、ツールの更新による出力形式の変更。Markdownは生成しないためです。
- 生成日:スナップショットに含めないため、生成した日と別の日に実行しても差分になりません。
- 観点・関数の利用テーブル(参考情報):
--checkでは参考情報のSinkを使わず、最初から生成しません。「生成してから除外する」方式ではないので、参考情報の種類が増えても除外リストを保守する必要がありません。 - プレビューの機能(
--preview):スナップショットの内容を変えないため、--checkでは有効にしません。--checkと同時に指定しても効果が無く、runCheckがNote: --preview has no effect in --check mode.と表示します(関数が利用しているテーブルの抽出も行いません)。 - 個々のパーティション:スナップショットはパーティションキーだけを持ち、パーティションを持ちません。パーティションを自動で追加する運用でも、追加のたびに差分になりません。
- DBのマイナー版の更新:
database.jsonはメジャーバージョンだけを持つため、マイナー版を上げても差分になりません。
8データの持ち方
8.1 テーブルキーで繋ぐ
このツールはDBを読むだけなので、更新の整合性を守るための集約や状態遷移はありません。代わりに設計の軸になっているのがテーブルキー(TableKey=スキーマ名+物理テーブル名)です。カラム・制約・トリガー・関連・付帯情報は、所属するテーブルをオブジェクトの参照ではなくテーブルキーで指します。だから、別々のSQLで、別々の時点で取得した情報を、後からテーブルキーで引き合わせて1テーブル分に組み立てられます。
ColumnEntityはTableEntityへの参照を持たず、スキーマ名・テーブル名だけを持ちます。集合のクラス(Triggers・Partitions・ForeignKeys・ViewReferences等)はAbstractEntitiesを継承し、テーブルキーで引ける索引を作ってbelongingTo(table)で返します。関数・シーケンス・型はテーブルに属さないため、テーブルキーを持ちません。ForeignKeysとViewReferencesは索引を2つ持ちます。ForeignKeysは、参照元のテーブルキーで引く索引(自分が参照している関連)と、参照先のテーブルキーで引く索引(自分を参照している関連=被参照)です。被参照の索引からは自己参照を除きます(定義書の「外部キー情報」と「被参照情報」に二重に載らないように)。ViewReferencesも同じ形で、ビューのテーブルキーで引く索引(belongingTo。ビューが参照するテーブル)と、参照されるテーブルのテーブルキーで引く索引(referencingTo。そのテーブルを参照しているビュー)を持ちます(9.8節)。
8.2 取得の単位と、1テーブル分の組み立て
一括取得したExportTargetsと、チャンク単位で取得したTableDetailから、テーブルごとにTableDefinitionContent.assembleで「1テーブル分の出力内容」を組み立てます。これがテーブル定義書1ファイル、スナップショット1行に対応する書き出しの単位です。
TableDefinitionContentは「何を出力するか」だけを持ち、出力先は持ちません。同じrecordを、Markdownとスナップショットの両方のSinkが受け取ります。TableDefinitionContentの項目 | どこから求めるか | 使う場所(Markdown) |
|---|---|---|
table・columns・indexes・constraints | TableDetail(チャンク単位で取得) | テーブル情報・カラム情報・インデックス情報・制約情報 |
foreignKeys | ForeignKeys.physicalBelongingTo(table) | 外部キー情報(DBに実在する制約だけ) |
logicalRelations | ForeignKeys.logicalBelongingTo(table) | 論理リレーション情報 |
incomingRelations | ForeignKeys.referencingTo(table)(物理・論理の両方。自己参照を除く) | 被参照情報 |
referencedTables | ViewReferences.belongingTo(table)(ビューのときだけ) | 参照するテーブル |
referencingViews | ViewReferences.referencingTo(table)(出力対象のビューだけ) | 参照しているビュー |
triggers | Triggers.belongingTo(table) | トリガー情報 |
partitions | Partitions.belongingTo(table)(パーティション表のときだけ) | パーティション情報 |
annotation | Annotations.belongingTo(table)(無ければTableAnnotation.EMPTY) | テーブル説明・備考・カラム備考 |
viewpoints | Viewpoints.containing(table)(宣言順) | 所属する観点 |
baseInfo | ExportTargets.baseInfo()(DB名・DBMS種別・メジャーバージョン・生成日) | 基本情報 |
テーブル定義書のER図は、この項目の関連からではなく、MarkdownExportSinkが対象範囲全体のForeignKeysから求めるDiagramNeighborhoodで描きます(9.7節)。描画距離が2以上だと、関連先のテーブルが持つ関連まで要るためです。TableDefinitionContentはスナップショットと共有するため、Markdownだけが使う図の範囲は持ちません。
関数・プロシージャはテーブルに属さないため、テーブルキーでは束ねません。関数1つ分をFunctionDefinitionContent(function+tableUsage)として持ち、スキーマ単位で取得した定義本体から作ります。tableUsageは、--previewのときだけ定義本体から抽出し、そうでなければFunctionTableUsage.notAnalyzed()です。これが、関数の定義書1ファイル、functions.jsonlの1行(tableUsageは含めない)、functionTableUsages.jsonの1件に対応する書き出しの単位です(9.9節)。
8.3 「値が無い」の表し方
値が無いことの表し方は、段階ごとに決まっています。これを知っていると、nullチェックがほとんど無い理由と、どこで変換すべきかが分かります。
1/0で返す)はSQLとDTOの変換で吸収し、エンティティでは「空文字=値が無い」に揃えます。表示用の連結・エスケープ・○はテンプレートだけが行います。SQLでエスケープすると、スナップショットにまでMarkdownの記法が混ざるためです。| 値 | SQLの結果 | エンティティ | スナップショット | Markdown |
|---|---|---|---|---|
| コメントの無いカラムの論理名 | ''(PostgreSQLはcoalesce)/NULL(Oracle) | "" | 項目ごと省略 | 空のセル |
| 複合外部キーの列 | 'warehouse_code,zone_code' | List.of("warehouse_code", "zone_code") | ["warehouse_code","zone_code"] | warehouse_code,zone_code |
| トリガーのイベント | 'INSERT/UPDATE' | List.of("INSERT", "UPDATE") | ["INSERT","UPDATE"] | INSERT/UPDATE |
| トリガーの本体 | PL/SQLの文字列(Oracle。LONG型をlong_valueで読む)/''(PostgreSQL。本体は実行する関数が持つ) | 末尾の改行・空白を除いた文字列(stripTrailing) | "body":"…"(PostgreSQLは項目ごと省略) | 表の後に### トリガー名と```sqlのコードブロック(空なら出さない) |
| NOT NULL | true(Oracleは1) | true | "notNull":true(falseも出す) | ○(偽は空) |
| 区分 | 'materialized_view' | TableType.MATERIALIZED_VIEW(未知なら例外) | "type":"materialized_view" | materialized_view |
| カラム備考 | (SQLには無い。サイドカーYAML) | TableAnnotation.columnRemark(名前) | "remarks":"…" | 改行・|をエスケープして備考のセル |
8.4 サイドカーの3種類の流れ先
SidecarYamlRepositoryがYAMLをSidecar(record)へ読み込み、fetchTargetsの中で3つに分かれて流れます。YAMLの読み込みは型の変換に専念し、ルールはドメインのメソッドに任せています。
| 中身 | ルールを持つ場所 | 流れ先 |
|---|---|---|
Annotations(手動付帯情報) | キーの解析TableKey.parse、孤児の検出TableAnnotation.orphanColumnNames | ExportTargets.annotations → TableDefinitionContent.annotation |
論理リレーション(List<ForeignKeyEntity>) | 関連名の自動生成ForeignKeyEntity.resolveLogicalRelationName、多重度の既定値Cardinality.DEFAULT_FOR_LOGICAL_RELATION | ExportTargetConsistency.resolveForeignKeysで物理外部キーと同じForeignKeysへ合流 |
Viewpoints(観点) | 識別子・パターンの検証Viewpoint.of、所属の判定Viewpoint.contains | 観点ページ(ViewpointWriter)、参考情報(InsightWriter)、定義書の「所属する観点」 |
8.5 不変と null の約束
- recordはコンパクトコンストラクタでリストを
List.copyOfし、集合のクラスも変更できないリスト・マップだけを返します。生成した後に中身が変わることはありません。 Sidecar・TableAnnotationはnullを空へ揃え、Annotations.belongingToは付帯情報が無いテーブルにもTableAnnotation.EMPTYを返します。呼び出し側はnullを気にしません。Tables.bySchema()・ForeignKeys.groupBySchema()は、結果をフィールドに持たず呼ぶたびに作ります。利用側のスコープを抜けたら解放されるようにするためです(メモリ効率)。
9ドメインの中心ロジック
9.1 出力対象の絞り込み(TableScope・TableNamePatterns)
設定のschema・tableから入口で1回だけTableScopeを組み立て、テーブルごとにmatchesで判定します。スキーマの絞り込みはSQLのINで、テーブル名パターンの判定はJavaで行います(ワイルドカード・除外はSQLの完全一致では表せないため)。
schema | table | 対象になるテーブル |
|---|---|---|
| 未指定 | 未指定 | すべて(PostgreSQLはpg_catalog・information_schemaを除く、OracleはOracleが管理するスキーマを除く) |
| 指定 | 未指定 | スキーマ名が一致するもの |
| 未指定 | 指定 | テーブル名パターンが一致するもの |
| 指定 | 指定 | 両方が一致するもの |
テーブル名パターン(TableNamePatterns)の規則は、出力対象の範囲と観点の所属テーブルで共通です。
*は0文字以上の任意の文字列。ほかの文字はそのまま一致させる。- 先頭の
!は除外。除外は包含より優先する。包含が1件も無ければ、除外に一致しない全テーブルが対象。 スキーマ.テーブルでスキーマを限定する。.が無ければ全スキーマ。- テーブル名・スキーマ名の部分が空のもの(
!だけ、sample.、.employee)は誤り。どのテーブルにも一致しない書き誤りだからです。
| パターン | sample.employee | sample.employee_profile | sample.employee_directory_view |
|---|---|---|---|
employee | ○ | ||
employee* | ○ | ○ | ○ |
employee*,!*_view | ○ | ○ | |
other.employee* |
TableScope.isFiltered()(スキーマかテーブルが指定されている)は、突き合わせの通知を抑えるかの判断に使います。絞り込みで外れたのか、リネーム・削除で乖離したのかを区別できないためです(次節)。
9.2 関連の合流と突き合わせ(ExportTargetConsistency)
外部キー制約を張らないDBでは、カタログの外部キーだけではER図がほとんど空になります。そこでサイドカーで宣言した論理リレーションを、物理の外部キーと同じ集合(ForeignKeys)へ合流させます。合流させることで、ER図のグループ分割(連結成分)、スキーマ跨ぎの関連の抽出(crossSchema)、被参照(referencingTo)に、追加の実装なしで反映されます。読み手が「DBに制約がある」と誤読しないよう、区別は2箇所で付けます。
- 定義書のセクション:
physicalBelongingToは「外部キー情報」へ、logicalBelongingToは「論理リレーション情報」(注意書き付き)へ。 - ER図の線種:
RelationTypeが線種を持ち、物理は実線--、論理は破線..。
合流の前に、参照元・参照先の両方が出力対象のテーブルにある関連だけを残します(片側だけ残すと、定義書の無いテーブルがER図にだけ箱として現れるため)。除いたことを知らせるかは由来で違います。突き合わせの通知の一覧です。
ConsistencyNotice.Kind | 重要度 | 出す条件 | 検出するメソッド |
|---|---|---|---|
UNRESOLVED_FOREIGN_KEY | WARN | 物理外部キーの片側が出力対象に無い。絞り込んでいるときは意図した除外なので出さない | resolveForeignKeys |
UNRESOLVED_LOGICAL_RELATION | WARN | 論理リレーションの片側が無い。絞り込みか乖離かを区別できないため常に出す | resolveForeignKeys |
LOGICAL_RELATIONS_MERGED | INFO | 論理リレーションを宣言していれば、合流した件数を報告 | resolveForeignKeys |
ORPHAN_TABLE_ANNOTATION | WARN | 実在しないテーブルへの付帯情報(絞り込んでいないとき) | findOrphanTableAnnotations |
ORPHAN_TABLE_ANNOTATION_CHECK_SKIPPED | INFO | 絞り込んでいるため上の検出をしなかった | findOrphanTableAnnotations |
ORPHAN_COLUMN_ANNOTATION | WARN | 実在しないカラムへのカラム備考(出力対象のテーブルについて、チャンクごと) | findOrphanColumnAnnotations |
UNMATCHED_VIEWPOINT_PATTERN | WARN | どのテーブルにも一致しない観点のパターン(絞り込んでいないとき) | findUnmatchedViewpointPatterns |
9.3 多重度の判定(Cardinality)
外部キーそのものは「関連がある」ことしか示しません。多重度は、参照元(子)の外部キー列に付いた制約から機械的に決めます。SQLが2つの真偽値を返し、ForeignKeyDto.toEntityがCardinality.of(unique, mandatory)で多重度にします。
| 外部キー列が一意 | すべてNOT NULL | Cardinality | 表記 | サンプルの例 |
|---|---|---|---|---|
| × | ○ | ONE_TO_MANY(1対多) | ||--o{ | employee.department_id → department |
| × | × | OPTIONAL_ONE_TO_MANY(0..1対多) | |o--o{ | employee.manager_id → employee(自己参照) |
| ○ | ○ | ONE_TO_ONE(1対1) | ||--o| | employee_profile.employee_id → employee |
| ○ | × | OPTIONAL_ONE_TO_ONE(0..1対1) | |o--o| | employee.parking_spot_id → parking_spot |
- 一意性は列名の一致ではなく包含関係で判定します(
FK(a, b)にUNIQUE(a)があれば外部キー全体も一意)。一意索引も含め、条件付きの部分インデックスは除きます(PostgreSQLのmapperのchild_key_unique)。 - 子側が「1以上」になることはありません。「親1件につき子が1件以上」はテーブル定義で表せないためです。
- 論理リレーションはDBに制約が無いので判定できず、YAMLの
cardinality(Cardinality.fromLabel)か、既定値の1対多を使います。サンプルのaudit_log.record_id→employeeは0..1対多と宣言されています。
9.4 ER図の出し分け(ForeignKeyGroups・RenderingPlan)
Mermaidは大きすぎる図を描けないため、スキーマ別ER図1枚のテーブル数に上限(erDiagramMaxNodes、既定80、0以下は上限なし)があります。「ノード」は関連を持つテーブルで、関連の無いテーブルは図に描きません(全テーブルはテーブル一覧に載る)。ページ構成はForeignKeyGroups.composeが1回で決め、各ページで描くか省くかはForeignKeyGroup.planRenderingが決めます。Writer・テンプレートはその結果に従うだけです。
--er-diagram-max-nodes=5で出力した実際の結果です。3つのビュー(マテリアライズドビューを含む)は関連を持たないのでノードに数えません。- 連結成分(
connectedComponents):外部キーの両端を同じまとまりとして併合するUnion-Find(経路圧縮付き)。a→bとb→cは1つの成分になります。並びはノード数の降順、同数なら先頭のテーブル名の昇順で、何度実行しても同じです。 - まとめ直し(
pack):成分ごとに1ページにすると2テーブルだけの図が大量にできるため、上限に収まる限り複数の成分を同じページに入れます(先頭から空きのあるグループに入れる貪欲法)。同じページの成分同士は線で繋がらないので、関連を誤読するおそれはありません。 - 分けても1つにしかならない場合は、スキーマのページと同じ内容のグループページができるだけなので、分けずに
Singleにします。この場合は上限を超えているのでOmitになり、図の代わりに外部キー一覧を載せます。 - 観点ページは人が選んだまとまりなので分けません(
ViewpointContent.relationsにplanRenderingだけを使う)。テーブル定義書のER図は分割せず、上限は描画距離を縮めるかの判断にだけ使います(9.7節)。
// 上限5で出力したスキーマ別ER図(erDiagram_testdb_sample.md)の本文
## ER図
ER図に描画するテーブル数が12件となり、上限(erDiagramMaxNodes = 5件)を超えるため、
外部キーで繋がったテーブルのまとまりごとに2個のグループへ分割しました。
## グループ一覧
| No. | テーブル数 | 外部キー数 | 主なテーブル | Link |
|:---|:---|:---|:---|:---|
|1|10|10|sample.employee|[■](./erDiagram_testdb_sample_group1.md)|
|2|2|1|sample.shipment|[■](./erDiagram_testdb_sample_group2.md)|
「主なテーブル」はForeignKeyGroup.mainTableで、最も多くの関連が接続するテーブルです。group1の外部キー数10には、論理リレーションaudit_log.record_idも含まれます(合流しているため)。
9.5 ER図の箱に何を表示するか(DiagramBoxes)
ER図はチャンクより前(writeOverview)に書きますが、箱にはカラムを表示します。そこでfetchDiagramBoxesが、関連を持つテーブルのカラムだけをスキーマ・チャンク単位で別に取得し、DiagramBoxes.Builderが関連カラム(関連の参照元・参照先として使われるカラム)だけを残して他を捨てます。
relationColumnsOf(drawnRelations)は、その図に描く関連で使われるカラムだけをテーブルごとに返し、参照元のカラムにFKを付けます(DiagramColumn)。- テーブル定義書のER図では、自テーブルだけは全カラム(
TableDetailのカラム)を表示し、相手のテーブルは関連カラムだけを表示します。 - 箱の表示名は
MermaidSupport.assignLabelsが決めます。ふつうはテーブル名で、同じ図の中に同名のテーブルが複数のスキーマにあるときだけスキーマ.テーブルにし、論理名があれば「(論理名)」を付けます。 - Mermaidの識別子は記号を
_に置き換えるため、別のテーブルが同じ識別子になり得ます(日本語の同じ文字数のテーブル名、sales.archive_ordersとsales_archive.orders等)。MermaidSupport.assignNodeIdsが_2・_3を付けて重複を避けます。スキーマ別ER図・観点のER図・テーブル定義書のER図のすべてがこれを使い、識別子を作るmermaidIdはprivateにして素通りできないようにしています(別のテーブルが1つの箱にまとまり、自己参照の線が引かれるのを防ぐ)。 - 図の本体(別名の宣言・関係線・箱)は
MermaidSupport.erDiagramCodeBlockが組み立てます。テーブル定義書のER図とスキーマ別ER図で同じ手順を使うため、関係線の向き(参照先→参照元)を2か所で揃えておく必要がありません。
9.6 長い表のページ分割(PagedSectionWriter)
GitHubで表として表示できる行数には限りがあるため、行が多くなり得る一覧はすべてPagedSectionWriterを通して書きます。3,000行以下なら本体ページに表を埋め込み、超えるなら{本体のファイル名}_{n}.mdへ分けて本体にはリンクだけを載せます。行の文字列はページ単位で作り、全行分を同時にメモリに持ちません。対象は、テーブル一覧、ER図の掲載テーブル・外部キー一覧、スキーマ跨ぎの外部キー、トリガー/関数/シーケンス/型の一覧、観点の所属テーブル・観点一覧です。
9.7 テーブル定義書のER図の描画距離(DiagramNeighborhood)
テーブル定義書のER図は、既定では自テーブルの関連だけを描きます。設定のoutput.erDiagramDistance(--er-diagram-distance。1〜3、既定1)を上げると、関連先のテーブルが持つ関連まで描きます。どこまで描くかはForeignKeys.neighborhoodOfが決め、結果をDiagramNeighborhood(描く関連・描いた距離・指定された距離)で返します。
- たどる:自テーブルから関連を向きを問わず幅優先でたどります(参照先の索引と被参照の索引の両方を見る)。距離nなら、n−1段目までに到達したテーブルが持つ関連を、近い順に集めます。距離1は自テーブルの関連だけで、従来の出力と同じです。
- 縮める:図のテーブル数が
erDiagramMaxNodes(NodeLimit)を超えるなら、超えなくなるまで距離を1つずつ縮めます。1でも超える場合は1で描きます(自テーブルの関連は省かない)。 - 知らせる:縮めた場合(
isShortened)は、TableDefinitionTemplates.erDiagramが図の前にその旨を書きます。
サンプルのdepartmentを距離2で出力すると、関連先のemployeeが持つ関連(自己参照・attendance・employee_history・employee_profile等)まで描かれ、図のテーブルは2から8に増えます。距離3と上限5を指定すると、上限を超えるため1段で描き、次の1文が付きます。
## ER図
関連するテーブルが多く、3段先までを描画するとテーブル数が上限(erDiagramMaxNodes)を超えるため、1段先までを描画しています。
```mermaid
erDiagram
sample_department["department"]
sample_employee["employee(従業員)"]
sample_department ||--o{ sample_employee : "employee_department_id_fkey"
…
上限を3にしているのは、関連の多いDBでは段数を増やすと図がすぐに大きくなり読めなくなるためです(DbxrayProperties.MAX_ER_DIAGRAM_DISTANCE)。範囲外の値は既定値に置き換えず、設定の誤りにします。図の関連線は、どの段の関連も「参照先 → 参照元」の向きで同じMermaidSupport.relationLineで描きます。
9.8 ビューが参照するテーブル(ViewReferences)
ビュー・マテリアライズドビューが参照するテーブル(ビューを含む)は、ビューのSQLを解析せず、DBが保持する依存関係から取得します(PostgreSQLはpg_depend・pg_rewrite、OracleはALL_DEPENDENCIES)。1件がViewReferenceEntity(参照する側のビューと、参照されるテーブル)で、集合がViewReferencesです。
- 取得の範囲:外部キーと同じく対象範囲全体を一括で取得し(
SchemaExportPipeline.fetchViewReferences)、参照する側が出力対象のビューのものだけを残します。参照される側は、出力対象外のスキーマのテーブルも事実として残します(サンプルのattendance_monthly_viewはsample_archive.departmentを参照)。 - SQLで揃えること:同じテーブルへの参照は1件にまとめ、自身への参照とDBMSが管理するスキーマのテーブルへの参照は含めません。パーティションへの参照は、パーティション表(根)への参照とします。
- 両方向で使う:ビューの定義書の「参照するテーブル」とスナップショットの
referencedTables(belongingTo)、テーブルの定義書の「参照しているビュー」(referencingTo。テーブルを変えたときに影響を受けるビューを知るため)。逆向きはスナップショットに持たせず、mcp-serverがreferencedTablesから逆引きします(get_tableのreferencedByViews)。
// view/attendance_monthly_view.md の「参照するテーブル」
| No. | 参照先 | 区分 |
|:---|:---|:---|
|1|sample.attendance|table|
|2|sample.employee_directory_view|view|
|3|sample_archive.department|table|
9.9 関数・プロシージャが利用しているテーブルの抽出(--preview)
ビューが参照するテーブル(9.8節)と違い、DBは関数・プロシージャの依存関係を保持しません。そこで、定義本体(FunctionEntity.definition)のSQLを読んで、利用している出力対象のテーブルと操作(C・R・U・D)を機械的に拾います。DBが持つ事実ではない参考値で、精度にリスクがあるため、--previewを付けたときだけ動くプレビューの機能(PreviewFeature.FUNCTION_TABLE_USAGE。互換性の対象外)として入れています。
設計の軸は2つです。
- 完全な構文解析はしない:「このキーワードの後ろの名前はテーブル」というきっかけと、前後の字句だけで判定します。構文解析器という依存を増やさず、構文の誤りや未対応の構文があっても止まらずに次の文から読み直せるようにするためです。呼び出している関数・動的SQLの中の参照は追いません(含まないことを定義書・MCPのツールの説明に書きます)。
- どんな入力でも例外を投げず、入力の長さに比例する時間で終える:定義本体はDBでCREATEの権限を持つ人が書ける入力です。正規表現を使わず1文字ずつ読み(
ArchitectureTestが検査。3章)、閉じていない文字列・コメントは例外にせず「読み切れなかった」印にします(コンパイルに失敗した本体もDBに残り得るため)。
FunctionTableUsageAnalyzer.analyzeの中で、右の列は各段が次の段へ渡す値です。どの段も、解析できない場合は例外ではなく、FunctionBodyの解析しない結果(NotAnalyzable・UnsupportedLanguage)・FunctionTableUsageのstatus(解析しなかった理由)で返します。抽出しない実行(--previewなし・--check)では、この枠の中を一切通りません。- 字句に分ける(
SqlLexer):先頭から1文字ずつ読み、今いる場所が文字列・コメント・識別子のどれかを状態として持ちます。コメントと文字列は互いの中で効かない(文字列の中の--はコメントにならず、コメントの中の'は文字列を始めない)ため、同時に左から読まないと区別できません。規則はDBで違います(PostgreSQLはブロックコメントの入れ子・ドル引用符・E'…'、Oracleはq'[…]'・条件付きコンパイル)。そのため、DBMS種別を文字列ではなくDbmsのenumにして、switchで網羅的に切り替えます(DBMSを足すとコンパイルエラーで気付ける)。 - 本体を切り出す(
FunctionBodyLocator):引数・戻り値の型の見出しは解析しません(RETURNS SETOF t・p t%ROWTYPEをテーブルの利用と誤らないため)。実装はDBMSごとです。
PostgreSQL(PostgresBodyLocator):pg_get_functiondefは本体を常にドル引用符で囲むため、見出しの後の最上位のドル引用符の中身を字句に分け直します。SQL標準の本体(BEGIN ATOMIC … END・RETURN 式)は、最上位のBEGIN ATOMIC・RETURNから終わりまでです。
Oracle(OracleBodyLocator):単独の関数・プロシージャは見出しのIS・ASの後ろ全体です。パッケージのサブプログラムは、どれも定義にパッケージ全体が入っているため、PackageSubprogramsがBEGIN・CASEとENDの対応を数えて、パッケージ本体の最上位のサブプログラムの範囲を求めます。オーバーロードは引数名で対応付け、区別できなければすべての本体からまとめて抽出します(overloadsMerged)。 - 拾う(
TableReferenceScanner):字句を先頭から1回なめ、きっかけのキーワードの後ろの名前と操作をTableReferenceにします。文の状態(StatementState)は;と最上位のLOOPで丸ごと作り直すため、括弧の数が合わない文があっても次の文から読み直せます。動的SQL(EXECUTE・EXECUTE IMMEDIATE・DBMS_SQL等)は、含むことだけをDynamicSqlKindで示します。 - 照らす(
FunctionTableUsageAnalyzer):拾った名前のうち、出力対象のテーブル(ビュー・マテリアライズドビューを含む)と一致したものだけを残します。CTEの名前・変数・DUAL等や、出力対象外のスキーマ・テーブルはここで落ちます。テーブルごとに操作をまとめ、スキーマが決まったものをスキーマ名・テーブル名の順に、決まらないものをその後に並べます。
操作とそのきっかけ。操作はCrudOperationです。細かい規則(括弧で囲んだJOIN・インラインビューへのDML・MERGEの各WHEN等)は、TableReferenceScannerTestのケースの一覧がそのまま仕様になっています。
| 操作 | テーブルの名前の前にあるきっかけ |
|---|---|
| C(作成) | INSERT INTO(OracleのINSERT ALL/FIRSTの各INTO、MERGEのWHEN NOT MATCHED THEN INSERTを含む) |
| R(読み取り) | FROM(カンマ区切りの並び)・JOIN・APPLY(Oracle)・USING(DELETE・MERGE)。括弧で囲んだFROM (a JOIN b ON …)の先頭や、SELECT … FOR UPDATEもR |
| U(更新) | UPDATE(MERGEのTHEN UPDATE、ON CONFLICT DO UPDATEを含む) |
| D(削除) | DELETE(MERGEのTHEN DELETEを含む)・TRUNCATE |
権限・トリガー・行ロックの構文にもINSERT・UPDATE・DELETE・TRUNCATE・EXECUTEの語が現れます(GRANT SELECT, TRUNCATE ON a, b TO r・CREATE TRIGGER … BEFORE TRUNCATE ON t・GRANT EXECUTE等)。直前の語(GRANT・ON・BEFORE等)でこれらを文として読まず、後ろのカンマ区切りの名前に操作を付けないようにしています。
スキーマ修飾の無い名前。スキーマは、定義から決まる場合だけ決めます。決まらない場合は、同じ名前の出力対象のテーブルがあるスキーマをすべて候補として持ち、候補が1つでもそのスキーマに決めつけません(SchemaResolution・TableUsage.schemaCandidates)。
| DBMS | 定義から決まる場合 | 決まらない場合(SchemaResolution) |
|---|---|---|
| PostgreSQL | 関数の属性としてSET search_pathが指定されていれば(FROM CURRENTは作成時の値に展開された形)、その並びの先頭から、出力対象のテーブルがあるスキーマにする($user・pg_catalog・pg_tempは除く)。空('')なら出力対象のスキーマが無く、DEFAULTは指定なしと同じ。本文中のSET文・set_config・EXECUTEで組み立てたSET文(変数でスキーマを渡す場合を含む)は読まない | 属性の指定が無ければ(本文で変更していても)実行時のsearch_pathで決まる(search_path)。指定がある場合に並びのどこにも無い名前は、出力対象外のものとして載せない |
| Oracle | 定義者権限(既定)は、所有者のスキーマにする | 定義者権限で所有者のスキーマに無い名前は、シノニム経由の可能性がある(synonym。シノニムの参照先は照らさない)。実行者権限(AUTHID CURRENT_USER)は実行するユーザーで決まる(current_user) |
解析しなかった・限界がある場合。状態はTableUsageStatusで表し、定義書では表の代わりに(または表の前に)その旨を書きます。
| 状態・印 | 意味 |
|---|---|
| NOT_ANALYZED | 抽出を行わない実行(--previewなし・--check)。定義書の節も参考情報も出さない |
| ANALYZED | 解析した(利用しているテーブルが無い場合を含む)。dynamicSql・incomplete(定義本体を最後まで読めなかった)・overloadsMergedがあれば、表の前に注記する |
| UNSUPPORTED_LANGUAGE | 言語がSQL・PL/pgSQL・PL/SQL以外(C・plpython3u・Javaの呼び出し仕様等) |
| NO_DEFINITION WRAPPED | 定義本体を取得できなかった(権限が無い等)/wrap(難読化)されている(Oracle) |
| SUBPROGRAM_NOT_FOUND | パッケージ本体からサブプログラムの本体を見つけられなかった(Oracle) |
サンプルのclose_project(プロシージャ。--previewで出力した関数の定義書から抜粋)と、スキーマが決まらない例のcount_rows(functionTableUsages.jsonから抜粋)です。
// function/close_project.md
## 利用しているテーブル
定義本体から機械的に抽出した参考値です。呼び出している関数・プロシージャの中の参照と、動的SQLの中の参照は含みません。
| No. | テーブル | 区分 | C | R | U | D |
|:---|:---|:---|:---|:---|:---|:---|
|1|sample.audit_log|table|○|○|||
|2|sample.employee|table|||○||
|3|sample.project|table||○|||
|4|sample.project_assignment|table||○||○|
// insights/testdb/sample/functionTableUsages.json の functions のうち count_rows(整形)
{"name":"count_rows", "arguments":"p_table_name text", "definitionHash":"a600475c…",
"status":"analyzed",
"tables":[{"name":"employee", "schemaResolution":"search_path",
"schemaCandidates":["sample"], "operations":["R"]}],
"dynamicSql":["EXECUTE"]}
参考情報は、古い結果を残さない・返さない
InsightWriter.writeFunctionTableUsagesは、抽出した実行(FunctionTableUsage.isAttempted())ではスキーマごとにfunctionTableUsages.jsonを書き、抽出していない実行では同じファイルを削除します。残すと、定義書には節が無いのにMCPサーバーが前回の結果を返し、テーブルや定義が変わった後の結果を最新のものとして答えてしまうためです。このファイルの削除は、cliでFileRepository.deleteFileを呼ぶ唯一の箇所で、消すのはOutputPathResolverで解決した1ファイルだけです(ディレクトリの削除は--rm-distの出力先に限ります)。
それでも、--rm-distを付けずに対象のスキーマを減らした場合などは、そのスキーマの参考情報だけが前回のまま残ります。そこで参考情報は、抽出元の定義本体のSHA-256(definitionHash)を持ち、MCPサーバーはスナップショットの定義本体のハッシュと一致するときだけ返します(mcp-server 読解ガイドの7.10節)。DB名・スキーマ名は、ファイル名に使うために置き換えられるため、ファイルの中のdatabase・schemaが実際の名前です。
精度をどう確かめているか。精度にリスクがある機能なので、テストの種類を分けています。サンプル(PostgreSQLの関数11件・Oracleのサブプログラム6件)は、全件がDDLから見て正しいことを確かめてあり、OracleはALL_DEPENDENCIESの参照先とも一致しました(PR #137の手元の確認)。実際の業務DBでの精度の確認は、プレビューを外す前に行う予定です(Issue #134)。
| テスト | 確かめること |
|---|---|
| TableReferenceScannerTest FunctionBodyLocatorTest | 判定・切り出しの規則を、表駆動のケースの一覧にしている。一覧を読むと規則を仕様として追える |
| TableUsageMetamorphicTest SqlTransforms | 全ケースを、改行・空白・コメント・大文字小文字で変形しても、コメント・文字列の中にDMLを足しても、結果が変わらない |
| GeneratedPackagesTest | 文法から生成した妥当なOracleのパッケージ3,000個で、サブプログラムごとの結果を正解と照らす。変形テストは字句の分け方に依存するため、範囲の切り出し(PackageSubprograms)の誤りはこちらで検知する |
| SqlLexerPropertyTest FunctionTableUsageAnalyzerRobustnessTest | ランダム・壊れた・悪意のある入力でも、例外を投げず、入力の長さに比例する時間で終える |
| RealisticFunctionBodiesTest | レガシーなシステムにありがちな長い本体(日本語のコメント・コメントアウトした古いコード・入れ子のループ・多数のサブプログラム) |
| FunctionTableUsageTemplatesTest MarkdownInjectionTest | 節の組み立てと、テーブル名・関数名がMarkdownのエスケープを通ること |
| SampleDatabaseExportIT OracleSampleDatabaseExportIT | 実DBのサンプルの関数に対する結果を、差分のベースライン(docs/sample/*/output-preview)と比べる |
10出力物とソースの対応
出力する各ファイルについて、書き出すメソッドと、中身を組み立てるクラスの対応です。Markdownは「Writerが段取りし、Templatesが文字列を組み立てる」、スナップショットは「Writerがrecordを作ってJSONにする」という分担です。
| 出力ファイル | 書き出すメソッド | 組み立てるクラス | 書く時点(Sink) |
|---|---|---|---|
| Markdown(MarkdownExportSink。出力先は {output.path}/{DB名}/) | |||
README.md | ReadmeWriter.writeReadme | ReadmeTemplates(集計の表はmetrics) | writeSummary |
tableList_{DB名}.md | TableDefinitionWriter.writeTableDefinitionList | TableDefinitionListTemplates | writeOverview |
{スキーマ}/{区分}/{テーブル}.md | TableDefinitionWriter.writeTableDefinition | TableDefinitionTemplates・MermaidSupport | writeTableDefinition |
erDiagramList_{DB名}.md | ErDiagramWriter.writeErDiagram(索引) | ErDiagramTemplates | writeOverview |
erDiagram_{DB名}_{スキーマ}.md・_group{n}.md | ErDiagramWriter.writeErDiagram(スキーマごと) | ErDiagramTemplates・MermaidSupport | writeOverview |
viewpointList_{DB名}.md・viewpoint_{DB名}_{id}.md | ViewpointWriter.writeViewpoints | ViewpointTemplates(図と表はErDiagramTemplatesを再利用) | writeOverview |
triggerList・functionList・sequenceList・typeList_{DB名}.md | ObjectListWriter.write…List | ObjectListTemplates | writeOverview |
{スキーマ}/function/{名前}.md | ObjectListWriter.writeFunctionDefinition | ObjectDefinitionTemplates.functionFile・FunctionTableUsageTemplates(「利用しているテーブル」の節。--previewのとき) | writeFunctionDefinitions |
{スキーマ}/sequence/{名前}.md・type/{名前}.md | ObjectListWriter.writeSequenceDefinition・writeTypeDefinition | ObjectDefinitionTemplates | writeOverview |
…_{n}.md(分割ページ) | PagedSectionWriter.writePagedSection | PagedSectionTemplates | 各一覧と同時 |
| スナップショット(SnapshotExportSink。出力先は {output.path}/snapshot/{DB名}/) | |||
database.json | SchemaSnapshotWriter.writeDatabase | DatabaseSnapshot | writeOverview |
{スキーマ}/tables.jsonl | SchemaSnapshotWriter.initTableFile・appendTable | TableSnapshot | beginSchemaTables・writeTableDefinition |
{スキーマ}/functions.jsonl | SchemaSnapshotWriter.writeFunctions | FunctionSnapshot | writeFunctionDefinitions |
{スキーマ}/sequences.jsonl・types.jsonl | SchemaSnapshotWriter.writeSequences・writeTypes | SequenceSnapshot・TypeSnapshot | writeOverview |
| 参考情報(InsightExportSink。出力先は {output.path}/insights/{DB名}/) | |||
viewpoints.json | InsightWriter.writeViewpoints | ViewpointsInsight・ViewpointInsight | writeOverview |
{スキーマ}/functionTableUsages.json | InsightWriter.writeFunctionTableUsages(--previewのとき。抽出していない実行は前回のファイルを削除) | FunctionTableUsagesInsight | writeFunctionDefinitions |
どの一覧を出すかはMarkdownExportSinkFactory.listDocumentsが1箇所で決めます。テーブル一覧は常に、ER図一覧はテーブルがあれば、関数・シーケンス・型・トリガーの一覧は対象が1件以上あれば、観点一覧は観点を宣言していれば出します。READMEのリンクとテーブル一覧の「関連ドキュメント」もこの結果を使うため、空の一覧を出したり、存在しない一覧へリンクしたりしません。
テーブル定義書のセクション
TableDefinitionWriter.writeTableDefinitionは、テンプレートのメソッドが返す文字列を次の順に並べて1ファイルにします。空文字を返したセクションは出力されません。
| 順 | セクション | TableDefinitionTemplatesのメソッド | 出す条件 |
|---|---|---|---|
| 1 | 見出し「テーブル名(論理名)」 | fileHeader | 常に |
| 2 | 基本情報(RDBMSとメジャーバージョン・DB名・作成日) | baseInfo | 常に |
| 3 | テーブル説明 | tableExplanation | 常に。本文はサイドカーに説明があるときだけ |
| 4 | テーブル情報 | tableInfo | 常に(備考はサイドカー) |
| 5 | 所属する観点 | viewpoints | 所属する観点があるとき |
| 6 | カラム情報 | columns | 常に(備考はサイドカー) |
| 7 | パーティション情報 | partitions | パーティション表のとき |
| 8 | ソース | view | ビュー・マテリアライズドビューのとき |
| 9 | 参照するテーブル | referencedTables | ビューが参照するテーブルがあるとき |
| 10 | インデックス情報 | indexes | 常に(0件なら見出しと表の頭だけ) |
| 11 | 制約情報 | constraints | 常に(同上) |
| 12 | 外部キー情報(物理) | foreignKeys | 常に(同上) |
| 13 | 論理リレーション情報 | logicalRelations | 論理リレーションがあるとき |
| 14 | 被参照情報 | incomingRelations | 参照されているとき |
| 15 | 参照しているビュー | referencingViews | 出力対象のビューから参照されているとき |
| 16 | トリガー情報 | triggers | 常に(同上。target.objectsでtriggerを外すと行が無くなる)。本体を持つトリガー(Oracle)は、表の後にトリガーごとの見出しと本体のコードブロック |
| 17 | ER図 | erDiagram | 常に(関連が無ければ「関連するテーブルはありません。」。描画距離は9.7節) |
| 18 | フッター(テーブル一覧へ) | footer | 常に |
関数・プロシージャの定義書のセクション
ObjectDefinitionTemplates.functionFileが、次の順に並べて1ファイルにします。「利用しているテーブル」は、--previewで抽出した実行だけの節で、FunctionTableUsageTemplates.sectionが空文字を返す(NOT_ANALYZED)と出力されません。関数・プロシージャ一覧(functionList_{DB名}.md)には列を足しません。
| 順 | セクション | 組み立てるメソッド | 出す条件 |
|---|---|---|---|
| 1 | 見出し(関数名) | functionFile | 常に |
| 2 | 基本情報 | baseInfo | 常に |
| 3 | 利用しているテーブル | FunctionTableUsageTemplates.section | --previewで抽出したとき。表(No.・テーブル・区分・C・R・U・D)の前に、動的SQL・読み切れなかった等の注記を置く。解析しなかった場合は、表の代わりにその理由。スキーマが決まらない名前は、表の後に候補を並べる |
| 4 | 定義 | functionFile | 常に(定義本体をcodeFenceで囲む) |
| 5 | フッター(関数・プロシージャ一覧へ) | functionFile | 常に |
Writer と Templates の分担
- Writerは「何を・どの順で・どのファイルに書くか」の段取りだけを持ちます。Markdownの文字列を自分で組み立てません。
- Templatesは副作用の無いstaticメソッドで、行・セクションの文字列を組み立てます。絞り込みやグラフの計算などのロジックは持ちません(それはdomain.modelの仕事)。
- 種別をまたいで使う部品は
writer.templateにあります。MarkdownTemplateSupport(基本情報のセクション、表の1行row、リンクのセル[■](…)、エスケープescapeTableCell・escapePipe・escapeHtml・escapeInline、コードブロック・コードスパンの囲みcodeFence・codeSpan、真偽値のmarker)、MermaidSupport(識別子・表示名・属性・関連線)、PagedSectionTemplates(見出し・ページ送り・戻るリンク)です。
ファイル名と相対リンクの規則
ファイル名・パス・リンクを、テンプレートやWriterがString.format等で直接組み立てることはしません。出力先の絶対パスとドキュメント間の相対リンクが同じ規則を参照するため、ファイル名を変えてもパスとリンクが食い違いません。
| クラス | 決めること | 例 |
|---|---|---|
DocumentLocations | Markdownのファイル名と、{DB名}/からの相対パス。ドキュメント間の相対リンク。関数のオーバーロードは{名前}_{番号}、観点ページは識別子から。DB由来の名前はPathSegments.encodeでパスの1要素にする。番号を付けた名前が別の関数と重なる場合(calcのオーバーロードと関数calc_1)はfunctionsSharingDefinitionFileで求めて警告する(ファイル名は互換性を約束する範囲のため、上書き自体は変えない) | sample/table/department.md、calculate_bonus_1.md |
OutputPathResolver(実装DefaultOutputPathResolver) | 出力先の絶対パス。未指定なら./output、{DB名}/を付ける。分割ページのパス。解決したパスが起点のディレクトリの外を指さないことを確かめる(within)。--rm-distで消してよいか(ルート、カレント・ホームとその上位のディレクトリは拒否) | ./output/testdb/sample/table/department.md |
SnapshotLocations | snapshot/の下の配置と、ファイル名から種別への逆引き(kindOf。比較で使う) | snapshot/testdb/sample/tables.jsonl |
InsightLocations | insights/の下の配置(snapshot/の兄弟)。関数の利用テーブルはスキーマごと | insights/testdb/viewpoints.json、insights/testdb/sample/functionTableUsages.json |
ListDocumentType | 一覧の種類ごとのファイル名の接頭辞とタイトル | FUNCTION → functionList_testdb.md「関数・プロシージャ一覧」 |
相対リンクは、リンク元が{DB名}/直下ならlinkFromDatabaseRoot(./を付ける)、{スキーマ}/{区分}/の下の個別定義書ならlinkFromDefinition(../../を付ける)で作ります。サンプルのdepartment.mdには[人事管理](../../viewpoint_testdb_personnel.md)・[テーブル一覧へ](../../tableList_testdb.md)が出力されています。
DB由来の文字列を信頼しない
DB名・スキーマ名・テーブル名・コメント・関数やビューの定義は、ツールを実行する人とは別の人(DBでCREATE・COMMENTの権限を持つ人)が書ける入力です。引用符で囲んだ識別子には/・..・改行・HTMLも書けるため、出力・パスに使う箇所はすべて決まった部品を通します(docs/development/coding-guidelines.mdの「セキュリティ」)。サイドカーYAMLは利用者自身が書くため、説明文はMarkdownとしてそのまま出力します。
| 使い道 | 通す部品 | 防ぐこと | 検査するテスト |
|---|---|---|---|
| ファイル名・ディレクトリ名 | PathSegments.encode(/ \ : * ? " < > |・~・制御文字を~+16進2桁に、.だけの名前と空の名前も置き換える。例:a/b → a~2Fb)と、DefaultOutputPathResolverのwithin | 出力先の外への書き込み(パストラバーサル) | OutputPathContainmentTest(OutputPathResolverの全メソッドを危険な名前で呼ぶ)、ArchitectureTest(Path.resolveの呼び出し場所) |
| 表の行 | row(表の行はすべてこのメソッドで組み立てる)。改行を<br>に、それ以外の<を<に。\で終わるセルの後には空白を1つ置く(GFMは\|を区切りではなく文字の|と読むため) | 生のHTMLの描画、行の分断、セルの結合による列のずれ | MarkdownInjectionTest(各テンプレートにHTML・```・見出しを混ぜた文字列を渡す) |
| 見出し・リストの項目 | escapeInline(改行を空白に) | 次の行がMarkdownの構文になること | |
| 関数・ビューの定義、トリガーの本体、パーティションキー | codeFence・codeSpan(中身の最長のバッククォートより長い囲み) | コードブロックが途中で閉じること | |
| Mermaidの図 | MermaidSupport(識別子は記号を_に、表示名・関連名の二重引用符と改行を置き換える) | 図の構文の破壊、コードブロックを閉じる行 | MermaidSupportTest |
| ログ | log4j2.xmlの%enc{%m}{CRLF} | 改行による偽のログ行 | — |
名前に上の文字を含まない限り、ファイル名は名前のままです。DB由来の文字列を受け取るテンプレートを足したら、MarkdownInjectionTestのrenderedDocumentsにも足します。関数の「利用しているテーブル」も、テーブル名・スキーマ名はescapeTableCell、候補の一覧と解析しなかった言語の名前はescapeInlineを通し、危険な文字列を入れた入力をMarkdownInjectionTestで確かめています。
11ソースごとの役割
main配下のクラスと、リソース・テストの一覧です。似た小さなクラスは1行にまとめ、package-infoは最後に1行でまとめています。「使う相手・使われる相手」の→は使う相手、←は使われる相手です。
| ファイル | 役割 | 使う相手・使われる相手 |
|---|---|---|
| com.dbxray(直下):エントリーポイント | ||
| Dbxray | main。処理全体を1つのtry-catchで囲み、通常実行(run)か差分検知(runCheck)を呼ぶ。依存関係をComponentsで2段階に組み立て、失敗はFailureReporterで報告して終了コードに変える | → CliArguments・DbxrayProperties・OutputDirectoryValidator・ConnectionSettings・MyBatisSqlSessionFactories・DatabaseTypeDetector・Components・Controller・FailureReporter |
| CliArgumentsパッケージプライベート | CLI引数の解析(--check・--rm-dist・--preview・--help・--version、接続情報と実行時設定の上書き値)。未知の引数は集めておき、requireKnownArgumentsで誤りにする(値は伏せて報告)。設定の引数名はDbxrayProperties.SETTINGSから、接続情報の引数名はConnectionSettings.CONNECTION_KEYSから引き、上書きできる引数の一覧OVERRIDE_ARGSにまとめる | ← Dbxray/→ DbxrayProperties・ConnectionSettings |
| DbxrayPropertiesパッケージプライベート | conf/config.ymlのうちdatabase以外の仕様(8項目の位置・CLI引数名・既定値・形式・範囲)と検証。CLI引数で上書きしてから検証し、誤りをまとめてInvalidConfigurationExceptionにする。2種類のRequestへ変換(--previewはpreviewFeatures=PreviewFeatureの全部へ) | ← Dbxray/→ ConfigFile・TargetSelection |
| OutputDirectoryValidatorパッケージプライベート | 出力先の検証(既存のファイルを指していないか、--rm-distで消してよいか)。DBへの接続前に、Componentsから取り出して使う | ← Dbxray/→ OutputPathResolver・FileRepository |
| Components | 依存関係をnewで組み立てる(composition root)。DB種別に依存しない部品は接続前に、CatalogRepositoryとそれに依存するユースケースはcontroller(種別, sqlSessionFactory)で接続後に組み立てる | ← Dbxray/→ 全レイヤーの具象クラス |
| CliUsageパッケージプライベート | --help・--versionの文言。版はjarのマニフェスト(Implementation-Version)から | ← Dbxray |
| presentation(.dto・.type):コントローラーと報告 | ||
| ExportSchemaController | ユースケースを呼び、結果をResultDto・DiffCheckResultDtoにする。例外は捕まえない | ← Dbxray/→ 2つのUsecase・DiffReportFormatter |
| FailureReporter | 失敗の報告。[errmsg]と、表示に含まれない原因を[cause]で並べる。想定外の失敗はスタックトレースをログへ出し、画面にログの場所を示す | ← Dbxray.main/→ ProcessResult |
| DiffReportFormatterパッケージプライベート | --checkの結果を文面にする。3区分の一覧と、unified diff(1件200行・全体2000行まで) | ← Controller/→ DiffResult・ContentDiff |
| ResultDto DiffCheckResultDto | 処理結果のrecord。DiffCheckResultDtoは差分の有無から終了状態(exitStatus)を返す | ← Controller・Dbxray |
| ProcessResult | SUCCESS・FAIL。[result]:の行を組み立てる | ← 2つのDto・FailureReporter |
| ExitStatus | 終了状態と終了コード(0成功・1差分あり・2失敗) | ← Dbxray・DiffCheckResultDto |
| application:ユースケース | ||
| ExportSchemaUsecase | 通常実行。一括取得→ER図の箱→個別定義のファイルが重なる関数の警告→(--rm-distなら削除)→3つのSinkで書き出し。有効にしたプレビューの機能をINFOでログへ | ← Controller/→ SchemaExportPipeline・3つの…SinkFactory |
| CheckDocumentDiffUsecase | --check。スナップショットのSinkだけで一時ディレクトリへ書き、SnapshotDiffで比べる。プレビューの機能は渡さない(Set.of())。一時ディレクトリは必ず消す | ← Controller/→ SchemaExportPipeline・SnapshotExportSinkFactory・SnapshotDiff |
| SchemaExportPipeline | 両ユースケースが共有する取得と書き出しの段取り(fetchTargets・fetchDiagramBoxes・export)。通知と件数をログへ出し、生成日をClockから求める。関数は、スキーマごとにFunctionDefinitionContentを作る(exportFunctionDefinitions。--previewならFunctionTableUsageAnalyzerで抽出し、テーブル名の索引は全スキーマで1回だけ求める)。テーブルの詳細を書くたびにカラムをDatabaseMetrics.Builderへ数え、最後にwriteSummaryを呼ぶ | ← 2つのUsecase/→ CatalogRepository・SidecarRepository・ExportTargetConsistency・ExportSink・DatabaseMetrics |
| ExportSchemaRequest CheckDocumentDiffRequest | ユースケースへの入力のrecord。3層を同じrecordのまま通る。通常実行用は有効にするプレビューの機能previewFeaturesも持つ。--check用はerDiagramMaxNodes・erDiagramDistance・rmDist・previewFeaturesを持たない | ← DbxrayProperties → Controller → Usecase |
| PreviewFeature | --previewで有効にするプレビューの機能のenum(今はFUNCTION_TABLE_USAGE=functionTableUsage)。出力は互換性の対象外で、スナップショットは変えない | ← DbxrayProperties・ExportSchemaUsecase・SchemaExportPipeline |
| TargetSelection | 出力対象の絞り込み条件(TableScope+OutputObjectTypeの集合)。ofで文字列を型に変え、2種類の誤りをまとめる | ← DbxrayProperties・SchemaExportPipeline |
| domain.model.target:出力対象 | ||
| ExportTargets | 一括取得した出力対象の組(基本情報・テーブル・関連・トリガー・パーティション・ビューの参照・関数/シーケンス/型・付帯情報・観点) | ← SchemaExportPipeline → 各Sink |
| TableDefinitionContent | 1テーブル分の出力内容。assembleで組み立てる。定義書1ファイル・スナップショット1行に対応 | ← SchemaExportPipeline/→ TableDefinitionWriter・TableSnapshot |
| FunctionDefinitionContent | 関数・プロシージャ1つ分の出力内容(function+tableUsage)。定義書1ファイル・スナップショットと参考情報の1件に対応。withoutTableUsageは抽出しない実行用 | ← SchemaExportPipeline/→ 各Sink・FunctionTableUsagesInsight |
| TableScope | 出力対象の範囲(スキーマ名リスト+テーブル名パターン)。matches・isFiltered・schemaNames | ← TargetSelection・SchemaExportPipeline/→ TableNamePatterns |
| OutputObjectType | trigger・function・sequence・type。parse(空なら全種別、未知の名前は例外) | ← TargetSelection・SchemaExportPipeline |
| ConsistencyNotice | 突き合わせの通知(種類・文言)。重要度(INFO・WARN)は種類が決める | ← ExportTargetConsistency → SchemaExportPipeline.report |
| domain.model.table:テーブルとその付属物 | ||
| TableEntity | テーブル1件(table・view・materialized_view)。論理名・ビューの定義・パーティションキー。headerName・isPartitioned・isView | どこでも |
| TableKey | スキーマ名+テーブル名。テーブルの識別子で、カラム・関連・付帯情報とテーブルを結ぶ。parseで「スキーマ.テーブル」を解析 | どこでも |
| TableType | テーブルの区分のenum。未知の区分は読み込み時に例外 | ← TableDto・TableEntity |
| Tables | 出力対象のテーブルの集合。contains・find(テーブルキー)、bySchema・byTableName(呼ぶたびに作る。後者は利用テーブルの抽出でスキーマ修飾の無い名前を引く) | ← SchemaExportPipeline・ExportTargetConsistency・FunctionTableUsageAnalyzer・Writer等 |
| TableDetail | 1テーブル分のカラム・インデックス・制約。assembleAllで、まとめて取得した結果をテーブルごとに振り分ける | ← AbstractCatalogRepository・TableDefinitionContent |
| ColumnEntity IndexEntity ConstraintEntity | カラム・インデックス・制約1件 | ← TableDetail・テンプレート・TableSnapshot |
| Columns Indexes Constraintsパッケージプライベート | TableDetail.assembleAll専用の、テーブルキーで引ける集合 | ← TableDetail |
| TriggerEntity Triggers | トリガー1件と集合。イベントはリスト。定義はOracleでは宣言部だけで、本体(body)を別に持つ(PostgreSQLは空文字) | ← SchemaExportPipeline・TableDefinitionContent・ObjectListWriter |
| PartitionEntity Partitions | パーティション表の下位のパーティション1件と集合。根のテーブルキーで引く。名前は根のスキーマからの相対(displayName) | ← TableDefinitionContent・TableDefinitionTemplates |
| ViewReferenceEntity ViewReferences | ビューが参照するテーブル1件と集合。ビューと参照されるテーブルの両方のテーブルキーで引く(belongingTo・referencingTo) | ← SchemaExportPipeline・TableDefinitionContent・TableSnapshot |
| SchemaTableKeyed | スキーマ名・テーブル名を持つエンティティのインタフェース。tableKey()の既定実装 | ← 各Entity・AbstractEntities |
| AbstractEntities | エンティティのリストをテーブルキーで索引する集合の基底。belongingTo(table) | ← Triggers・Partitions・ForeignKeys・Columns等 |
| TableNamePatterns | テーブル名パターン(ワイルドカード・除外・スキーマ修飾)。出力対象の範囲と観点の所属テーブルで共有 | ← TableScope・Viewpoint |
| domain.model.relation:関連とER図のまとまり | ||
| ForeignKeyEntity | 関連1件(物理の外部キーと論理リレーションの総称)。参照元・参照先・列・多重度・由来。logical・resolveLogicalRelationName | どこでも |
| ForeignKeys | 関連の集合。参照元・参照先の2つの索引。physicalBelongingTo・logicalBelongingTo・referencingTo・groupBySchema・crossSchema・withinTables・crossingTableSetBoundary、テーブル定義書のER図の範囲(neighborhoodOf) | ← ExportTargetConsistency・TableDefinitionContent・MarkdownExportSinkFactory・ErDiagramWriter・Viewpoint |
| RelationType | 由来。PHYSICAL(実線--・物理)とLOGICAL(破線..・論理) | ← ForeignKeyEntity・Cardinality |
| Cardinality | 多重度。of(unique, mandatory)での判定、Mermaidの端点、日本語のラベル、論理リレーションの既定値 | ← ForeignKeyDto・SidecarYamlRepository・TableSnapshot |
| ForeignKeyGroup | 1枚のER図に描く関連の集まり。ノード(スキーマ名・テーブル名順)、上限との比較、planRendering、mainTable(外部キーが無いグループでは例外。グループ一覧に載るグループは必ず外部キーを持つ) | ← ErDiagramWriter・Viewpoint |
| ForeignKeyGroups | ページ構成の決定(compose)。連結成分(Union-Find)と、上限内でのまとめ直し(pack)。PageComposition(Single・Grouped) | ← ErDiagramWriter |
| NodeLimit | 1枚の図のノード数の上限。0以下は上限なしに揃える | ← MarkdownExportSinkFactory・ForeignKeyGroup等 |
| RenderingPlan | 描く(Draw)か省く(Omit)かの計画(sealed interface) | ← ErDiagramWriter・ErDiagramTemplates・ViewpointWriter |
| DiagramBoxes | ER図の箱の表示内容(論理テーブル名・関連カラム)。Builderが取得したカラムから関連カラムだけを残す | ← SchemaExportPipeline・ER図のWriter・テンプレート |
| DiagramColumn | 箱に表示するカラムと、FKか | ← DiagramBoxes・MermaidSupport・TableDefinitionTemplates |
| DiagramNeighborhood | テーブル定義書のER図に描く関連と、描いた距離・指定された距離。isShortenedで上限のため縮めたかを判定 | ← ForeignKeys.neighborhoodOf → TableDefinitionTemplates.erDiagram |
| domain.model.sidecar・viewpoint:サイドカーと観点 | ||
| Sidecar | サイドカーYAMLの内容(付帯情報・論理リレーション・観点)。nullを空へ揃える | ← SidecarYamlRepository → SchemaExportPipeline |
| Annotations | テーブルキーごとの手動付帯情報。無ければTableAnnotation.EMPTYを返す | ← SchemaExportPipeline・ExportTargetConsistency・TableDefinitionContent |
| TableAnnotation | 1テーブル分の説明・備考・カラム備考(記述順を保つ)。orphanColumnNames(記述順のList) | ← TableDefinitionTemplates・TableSnapshot |
| Viewpoint | 観点。of(識別子・パターンの検証)、contains、unmatchedPatterns、resolve(所属テーブル・内側の関連・境界の関連) | ← SidecarYamlRepository・ViewpointWriter・InsightExportSinkFactory |
| Viewpoints | 観点の集合(宣言順)。containing(table)でテーブルから逆引き | ← Sidecar・TableDefinitionContent・ViewpointWriter |
| ViewpointContent | 1観点分の出力内容(所属テーブル・ForeignKeyGroup・観点外との関連) | ← Viewpoint.resolve → ViewpointWriter・ViewpointsInsight |
| domain.model.schemaobject・database・document | ||
| FunctionEntity Functions | 関数・プロシージャ1件と集合。オーバーロードの番号・数、定義本体(一覧の取得では空)。schemaNamesは定義本体を取る単位 | ← SchemaExportPipeline・ObjectListWriter・FunctionSnapshot・DocumentLocations |
| SequenceEntity Sequences | シーケンス1件と集合。数値は桁あふれしないよう文字列のまま | ← ObjectListWriter・SchemaSnapshotWriter |
| TypeEntity Types | ユーザー定義型1件と集合 | ← ObjectListWriter・SchemaSnapshotWriter |
| Dbms | 接続先のDBMS種別のenum(POSTGRESQL・ORACLE)。表示名(PostgreSQL・Oracle)を基本情報・スナップショットに出し、種別そのものは定義本体の字句の規則(9.9節)の切り替えに使う。fromDisplayNameで、mapperが返す表示名から求める | ← DatabaseDto・DatabaseEntity/→ SqlLexer・各BodyLocator・TableReferenceScanner |
| DatabaseEntity BaseInfoEntity | DB名・DBMS種別(Dbms)・メジャーバージョン(カタログから)と、それに生成日を足した基本情報。BaseInfoEntity.dbmsNameは表示名 | ← AbstractCatalogRepository・SchemaExportPipeline/→ テンプレート |
| ListDocumentType | 一覧ドキュメントの種類(7つ)。ファイル名の接頭辞とタイトル | ← MarkdownExportSinkFactory・DocumentLocations・テンプレート |
| domain.model.metrics:出力対象の集計(DB単位のREADME) | ||
| DatabaseMetrics | スキーマごとの集計。カラム以外は一括取得したExportTargetsからbuilderの時点で数え、カラムはチャンクを書くたびにBuilder.collectColumnsで足す。集計用のSQLは使わない(テーブルの絞り込みは取得後に行うため、SQLで数えると出力したドキュメントと数が合わない)。isCountedで取得した種別かを判定する | ← SchemaExportPipeline・ExportSink.writeSummary・ReadmeTemplates |
| SchemaMetrics | 1スキーマ分の数(テーブル・パーティション表・ビュー・マテリアライズドビュー・カラム・関数・プロシージャ・シーケンス・型・トリガー・論理名を持つテーブルとカラム・外部キー・論理リレーション・関連を持たないテーブル・観点に所属するテーブル・説明を補ったテーブル)。plusで合計する。割合・閾値による良し悪しの判定は付けない | ← DatabaseMetrics・ReadmeTemplates |
domain.model.tableusage:関数・プロシージャの利用テーブル(--preview) | ||
| FunctionTableUsage | 1関数分の抽出結果(状態・解析しなかった言語・tables・dynamicSql・incomplete・overloadsMerged)。notAnalyzed(抽出しない実行)・analyzed等のファクトリ、isAttempted(節・参考情報を出すか)、schemaUndeterminedTables(決まり方ごとの行) | ← FunctionTableUsageAnalyzer・FunctionDefinitionContent/→ FunctionTableUsageTemplates・FunctionTableUsagesInsight |
| TableUsage | 利用しているテーブル1件。同じ名前の候補のテーブル・スキーマの決まり方・操作の集合。schemaDetermined・has・orderedOperations・tableType | ← FunctionTableUsageAnalyzer/→ FunctionTableUsage |
| CrudOperation DynamicSqlKind SchemaResolution TableUsageStatus | 操作(C・R・U・D)/動的SQLの種類(EXECUTE・EXECUTE_IMMEDIATE・DBMS_SQL・OPEN_FOR・DBLINK)/スキーマの決まり方(DETERMINED・SEARCH_PATH・CURRENT_USER・SYNONYM)/抽出の状態(NOT_ANALYZED・ANALYZED・UNSUPPORTED_LANGUAGE・NO_DEFINITION・WRAPPED・SUBPROGRAM_NOT_FOUND) | ← TableReferenceScanner・FunctionTableUsage・テンプレート |
| domain.model.snapshot・insight:機械が読む形 | ||
| DatabaseSnapshot | database.json(形式バージョン・DB名・DBMS・メジャーバージョン)。FORMAT_VERSIONはmcp-serverとの契約 | ← SchemaSnapshotWriter |
| TableSnapshot | tables.jsonlの1行。カラム・インデックス・制約・外部キー・論理リレーション・トリガー(Oracleは本体を含む)・付帯情報・ビューが参照するテーブル(referencedTables。項目の並びを変えないよう末尾)を項目として持つ。被参照と参照しているビューは持たない(参照元から導ける) | ← SchemaSnapshotWriter |
| FunctionSnapshot SequenceSnapshot TypeSnapshot | functions・sequences・types.jsonlの1行 | ← SchemaSnapshotWriter |
| SnapshotValuesパッケージプライベート | 空文字をnullにする(JSONで項目ごと省略させる) | ← 各Snapshot |
| SnapshotKind | tables・functions・sequences・types。identifyで行の表示名(関数は引数を含む) | ← SnapshotLocations・SnapshotDiff |
| DiffResult ContentDiff | 比較結果(生成側だけ・コミット側だけ・内容不一致)と、1件分のunified diff | ← SnapshotDiff → DiffReportFormatter |
| ViewpointsInsight ViewpointInsight | viewpoints.json(形式バージョン・観点・所属テーブル)。ViewpointContentから作る | ← InsightWriter |
| FunctionTableUsagesInsight | functionTableUsages.json(1スキーマ分。形式バージョン・DB名・スキーマ名・関数ごとの状態・利用テーブル・動的SQL・抽出元の定義本体のSHA-256)。FunctionDefinitionContentから作る。抽出していない関数は含めない | ← InsightWriter/mcp-serverが読む |
| domain.repository:外部とのやり取りのインタフェース | ||
| CatalogRepository | DBからの取得(12メソッド)。実装はDB種別ごと | ← SchemaExportPipeline/実装 AbstractCatalogRepository |
| SidecarRepository | サイドカーYAMLの読み込み。パスが空なら空のSidecar | ← SchemaExportPipeline/実装 SidecarYamlRepository |
| FileRepository | ファイル操作(書く・追記・ディレクトリ作成・存在・一覧・読む・一時ディレクトリ・再帰削除・ファイル1つの削除)。ファイル1つの削除(deleteFile)を呼ぶのはInsightWriterだけ | ← Writer・Usecase・SnapshotDiff・OutputDirectoryValidator/実装 LocalFileRepository |
| domain.service(export・target・snapshot・insight・path・直下) | ||
| ExportSink | 1つの出力形式で書き出すインタフェース(writeOverview・writeFunctionDefinitions(関数1つ分の出力内容FunctionDefinitionContentのリストを受ける)・beginSchemaTables・writeTableDefinition・writeSummary(集計DatabaseMetricsを受ける)) | ← SchemaExportPipeline |
| MarkdownExportSinkFactory | MarkdownのSinkを作る。出力する一覧の決定(listDocuments)、テーブル定義書のER図の範囲(neighborhoodOf)の決定と、5つのWriterの呼び出し。READMEはwriteSummaryで集計とともに書く | ← ExportSchemaUsecase/→ 5つのWriter |
| SnapshotExportSinkFactory | スナップショットのSinkを作る | ← 2つのUsecase/→ SchemaSnapshotWriter |
| InsightExportSinkFactory | 参考情報のSinkを作る。writeOverviewで観点を、writeFunctionDefinitionsでスキーマごとの関数の利用テーブルを書く | ← ExportSchemaUsecase/→ InsightWriter |
| ExportTargetConsistency | 出力対象と関連・付帯情報・観点の突き合わせ。関連の合流(resolveForeignKeys)と、孤児・不一致の検出。結果は通知で返す | ← SchemaExportPipeline |
| SchemaSnapshotWriter | スナップショットの書き込み。テーブルはinitTableFileで空にしてから1行ずつ追記 | ← SnapshotExportSinkFactory/→ SnapshotSerializer・FileRepository |
| SnapshotDiff | 2つのsnapshot/をオブジェクト単位で比べ、内容の違いにunified diffを付ける | ← CheckDocumentDiffUsecase/→ UnifiedDiffGenerator |
| SnapshotSerializer | recordとJSONの変換のインタフェース(serialize・deserialize・formatForDiff) | 実装 JacksonSnapshotSerializer |
| UnifiedDiffGenerator | 2つの行リストからunified diffを作る(Myers法、前後3行、外部ライブラリに依存しない) | ← SnapshotDiff |
| InsightWriter | viewpoints.jsonの書き込み(観点が無ければ書かない)と、functionTableUsages.json(スキーマごと)の書き込み。抽出していない実行では、後者の前回のファイルをdeleteFileで削除する | ← InsightExportSinkFactory |
| OutputPathResolver | 出力先パスのインタフェース(ベース・{DB名}/・各ドキュメント・分割ページ・snapshot/・insights/・参考情報の各ファイル)。--rm-distで消してよいか | 実装 DefaultOutputPathResolver |
| DocumentLocations | Markdownのファイル名・{DB名}/からの相対パス・相対リンクの規則。名前はPathSegmentsを通す。個別定義のファイルが重なる関数を求める(functionsSharingDefinitionFile) | ← DefaultOutputPathResolver・各テンプレート・ExportSchemaUsecase |
| SnapshotLocations InsightLocations | snapshot/・insights/の配置。ファイル名から種別を逆引き(kindOf) | ← DefaultOutputPathResolver |
| PathSegments | DB由来の名前をパスの1要素として安全な形にする(encode。区切り・Windowsで使えない文字・制御文字・~を~+16進2桁に) | ← DocumentLocations・SnapshotLocations・InsightLocations・DefaultOutputPathResolver |
| OutputRoot | 出力先ベースディレクトリと基本情報の組。常に対で使うため1つの値にしている | ← 各Sink・Writer・OutputPathResolver |
| domain.service.tableusage:関数・プロシージャの定義本体から利用テーブルを抽出(9.9節。正規表現を使わない) | ||
| FunctionTableUsageAnalyzer | 抽出の入口。of(tables, tablesByName, dbms)でスキーマごとに作り(テーブル名の索引tablesByNameは全スキーマで1回だけ求めて渡す)、analyze(function)で本体を切り出し→テーブルを拾い→出力対象のテーブルと照らす。スキーマが決まる条件(SET search_path・Oracleの所有者)と候補の示し方を持つ | ← SchemaExportPipeline/→ FunctionBodyLocator・TableReferenceScanner・Tables |
| SqlLexer | 定義本体を字句に分ける。1文字ずつ読み、コメント・文字列を同時に区別する。規則はDBごと(PostgreSQLの入れ子のブロックコメント・ドル引用符・E'…'、Oracleのq'[…]'・条件付きコンパイル)。閉じていない要素は例外にせずincomplete | ← 各BodyLocator/→ Dbms・SqlToken |
| SqlToken SqlTokenKind SqlLexResult | 字句1つ(元の文字列・位置・行番号・ドル引用符の中身の範囲)/字句の種類/字句の列とincomplete | ← SqlLexer・TableReferenceScanner |
| SqlTokensパッケージプライベート | 字句の列と、位置を指定した判定・名前の読み取り。範囲外の位置は判定が偽・取得がnullで、前後を覗く処理で位置の検査を繰り返さない | ← TableReferenceScanner・PackageSubprograms・各BodyLocator |
| SqlNames AsciiCase | 字句からカタログ上の名前(引用符を外し、引用符の無い名前はASCIIの英字だけ畳み込む)を求める/ASCIIの英字だけを対象にした大文字小文字の比較・変換(ıがIになる等を避ける) | ← TableReferenceScanner・各BodyLocator |
| FunctionBodyLocatorパッケージプライベート PostgresBodyLocator OracleBodyLocator | 定義から解析する本体を切り出すインタフェース(of(dbms)で実装を選ぶ)と、DBMSごとの実装。PostgreSQLはドル引用符の中身・SQL標準の本体、Oracleは単独の関数は見出しのIS・ASの後ろ、パッケージのサブプログラムはPackageSubprogramsで求めた範囲。言語が対象外・wrap・定義が無い場合は理由を持つ結果を返す | ← FunctionTableUsageAnalyzer/→ SqlLexer・PackageSubprograms |
| PackageSubprogramsパッケージプライベート | Oracleのパッケージ本体から、最上位のサブプログラムの範囲を求める。BEGIN・CASEとENDの対応をスタックで数え(CASEは文と式を区別)、条件付きコンパイルは分岐ごとに読む。範囲がずれると後ろのサブプログラムすべてに波及するため、テストが厚い | ← OracleBodyLocator |
| FunctionBodyパッケージプライベート | 切り出した結果。解析しない場合(NotAnalyzable・UnsupportedLanguage)と、DBMSごとの解析する本体(Postgres:字句・incomplete・searchPath/Oracle:字句・incomplete・overloadsMerged・invokerRights)を型で分ける(sealed interface) | ← 各BodyLocator/→ FunctionTableUsageAnalyzer |
| TableReferenceScannerパッケージプライベート | 字句から、テーブルを指す位置の名前と操作(C・R・U・D)・動的SQLを拾う。きっかけの語と前後の字句だけで判定し、完全な構文解析はしない。;と最上位のLOOPで文の状態を作り直す | ← FunctionTableUsageAnalyzer/→ SqlTokens |
| TableReference ScanResultパッケージプライベート | 拾った名前1件(スキーマ・テーブル・操作。出力対象と照らす前)/拾った結果(名前の列と動的SQLの種類) | ← TableReferenceScanner/→ FunctionTableUsageAnalyzer |
| domain.service.writer:Markdownの段取りとテンプレート | ||
| PagedSectionWriter | 3,000行を超える表を別ページへ分ける | ← 4つのWriter |
| TableDefinitionWriter | テーブル一覧と定義書の段取り(セクションの順) | ← MarkdownExportSinkFactory |
| TableDefinitionTemplates | 定義書の各セクション(エスケープ・○・参照するテーブル/参照しているビュー・トリガーの本体・描画距離に応じたテーブル単位のER図を含む) | ← TableDefinitionWriter/→ MermaidSupport |
| TableDefinitionListTemplates | テーブル一覧(関連ドキュメントへのリンクを含む) | ← TableDefinitionWriter |
| ErDiagramWriter | スキーマ別ER図・グループ別ER図・索引の段取り。composeの結果で振り分け、planRenderingに従う | ← MarkdownExportSinkFactory/→ ForeignKeyGroups |
| ErDiagramTemplates | スキーマ別ER図のページ(図の本体はMermaidSupport.erDiagramCodeBlock)、省略・分割の文言、グループ一覧(groupIndex)、掲載テーブル・外部キー一覧の表 | ← ErDiagramWriter・ViewpointTemplates |
| ViewpointWriter | 観点ページと観点一覧の段取り。観点ごとにresolveを1回だけ | ← MarkdownExportSinkFactory |
| ViewpointTemplates | 観点ページの各セクション。図と表はErDiagramTemplatesを再利用 | ← ViewpointWriter |
| ObjectListWriter | トリガー・関数・シーケンス・型の一覧と個別定義 | ← MarkdownExportSinkFactory |
| ObjectListTemplates ObjectDefinitionTemplates | 一覧の表/個別定義のページ(関数はFunctionDefinitionContentから作り、「利用しているテーブル」の節を差し込む) | ← ObjectListWriter |
| FunctionTableUsageTemplates | 関数の個別定義の「利用しているテーブル」の節。表の前に限界(該当なし・動的SQL・読み切れなかった・オーバーロードをまとめた)を示す文、スキーマが決まらない名前は(search_path)等のプレースホルダーと候補。解析しなかった場合は表の代わりに理由。テーブル名・候補は既存のエスケープを通す | ← ObjectDefinitionTemplates/→ MarkdownTemplateSupport |
| ReadmeWriter ReadmeTemplates | {DB名}/README.md(出力する一覧へのリンクと、出力対象の集計。集計の列は、target.objectsで外した種別を0ではなく列ごと省く) | ← MarkdownExportSinkFactory(writeSummary)/→ DatabaseMetrics |
| MarkdownTemplateSupport | 共通部品(基本情報・ファイルの見出しfileHeader・表の行row・リンクのセル・一覧へのリンクの箇条書きlistLinks・一覧への相対リンク・エスケープ・HTMLの無害化・コードブロック/コードスパンの囲み・SQLのコードブロックsqlCodeBlock・○) | ← 全テンプレート |
| MermaidSupport | Mermaid記法の部品(図の本体erDiagramCodeBlock・識別子の重複回避assignNodeIds・表示名・別名・属性・関連線)。二重引用符・改行を置き換える | ← ErDiagramTemplates・TableDefinitionTemplates |
| RowRenderer | 表の要素1つを1行にする関数(render(rowNumber, row)。行番号は1始まりで「No.」列に出す)。BiFunction<Integer, T, String>では行番号だと読めないため名前を付けている | ← PagedSectionWriter・ObjectListWriter・TableDefinitionTemplates |
| PagedSectionTemplates | 見出し・分割ページへのリンク・ページ送り・戻るリンク | ← PagedSectionWriter・テンプレート |
| infrastructure.db:DBへの接続と取得 | ||
| ConnectionSettings | 検証済みの接続情報。config.ymlのdatabase+環境変数のパスワード+CLI引数。driver・url必須、ファイルのpasswordとurlへのパスワードの埋め込みは誤り。接続情報のキー(CONNECTION_KEYS)と、上書きするCLI引数の名前の規則(--db-キー。cliArgumentName)もここで1か所に定義する | ← Dbxray・CliArguments |
| MyBatisSqlSessionFactories | mybatis-config.xmlからSqlSessionFactoryを作る(接続はしない) | ← Dbxray |
| DatabaseTypeDetector | 接続して製品名からDatabaseTypeを判定し、DBMS・ドライバの版をログへ残す。接続できない・非対応のDBはUserCorrectableException | ← Dbxray |
| DatabaseType | postgresql・oracleと、ドメインのDBMS種別(Dbms)の対応。名前はSQLの名前空間にも使う | ← Components・AbstractCatalogRepository |
| AbstractCatalogRepository | CatalogRepositoryの共通実装。SQLのIDを組み立てて実行し、DTO→エンティティ。SQLの失敗はどのSQLかを添えて包む | ← 2つの実装/→ 12のDTO |
| PostgresCatalogRepository OracleCatalogRepository | DB種別を渡すだけの実装 | ← DatabaseType |
| TableDto 等 12個のDTO | TableDto・ColumnDto・IndexDto・ConstraintDto・ForeignKeyDto・TriggerDto・PartitionDto・ViewReferenceDto・FunctionDto・SequenceDto・TypeDto・DatabaseDto。SQLの結果の受け皿(record)で、toEntity()で値の形を揃える。ForeignKeyDtoは多重度を判定し、TriggerDtoはイベントを分解して本体の末尾の空白を除く | ← AbstractCatalogRepository |
| DtoValuesパッケージプライベート | null・空白を空文字へ、区切り文字で連結された値をリストへ | ← 各DTO |
| infrastructure.file・path・snapshot | ||
| LocalFileRepository | FileRepositoryの実装(UTF-8で書く・追記・再帰削除・ファイル1つの削除等) | ← Components |
| SidecarYamlRepository | サイドカーYAMLの読み込み(SnakeYAMLのSafeConstructor)。ファイルが無い・YAMLとして読めないのは誤り(文言はYamlSyntaxErrors)、個々の記述の誤りは何件目の定義かを添えて警告し、読み飛ばす | ← Components/→ TableKey.parse・Viewpoint.of・Cardinality |
| DefaultOutputPathResolver | OutputPathResolverの実装。既定の出力先./output。解決したパスが起点の外を指さないことをwithinで確かめる。--rm-distでルート、カレント・ホームとその上位を拒否 | → DocumentLocations・SnapshotLocations・InsightLocations |
| JacksonSnapshotSerializer | SnapshotSerializerの実装(NON_EMPTY、差分表示用の整形) | ← Components |
| config・shared.exception | ||
| ConfigFile | 設定ファイル(--config、無ければconf/config.yml)をYAMLとして読む。仕様と検証は値を使う側が持つ | ← Dbxray・DbxrayProperties・CliArguments |
| YamlSyntaxErrors | SnakeYAMLの構文誤りを、ファイルの内容を引用しない文言(種類と位置)へ変える | ← ConfigFile・SidecarYamlRepository |
| InvalidConfigurationException | 設定の誤り(UserCorrectableExceptionの派生) | ← ConfigFile・DbxrayProperties・ConnectionSettings |
| UserCorrectableException | 利用者が直せる誤り。投げてよいのは入口と、入力・実行環境に触れるインフラだけ | ← CliArguments・OutputDirectoryValidator・DatabaseTypeDetector・SidecarYamlRepository・FailureReporter |
| package-info | 各パッケージの要旨。shared.exceptionだけは置いてよいものの規約も書く | — |
| リソース(modules/cli/src/main/resources) | ||
| mapper/postgresql/ catalogMapper.xml | PostgreSQLのカタログ(pg_catalog)を読む13のSELECT。パーティションの子・親から複製された外部キー・トリガーを除く | ← AbstractCatalogRepository(名前空間…postgresql.CatalogRepository) |
| mapper/oracle/ catalogMapper.xml | Oracleのディクショナリ(ALL_*)を読む13のSELECT。LONG型の列(ビューのソース・トリガーの本体等)とDBのバージョンはWITH FUNCTIONで読む | ← 同上(…oracle.CatalogRepository) |
| mybatis-config.xml | DTOの型の別名、接続(POOLED。${driver}等)、2つのmapper | ← MyBatisSqlSessionFactories |
| log4j2.xml | ログファイル(既定はこのツールがINFO・ライブラリがWARN。dbxray.log.levelで上げる)と、このツールのWARNの標準エラー出力。メッセージの改行を置き換える | — |
| conf/config.yml | 配布する設定(全項目が未指定。各項目の例をコメントで示す) | ← ConfigFile |
| テスト(modules/cli/src/test・modules/cli/src/integrationTest) | ||
| ArchitectureTest | 構造の規約(3章の表)をArchUnitで検査 | → main配下の全クラス |
| ComponentsTest | 両方のDB種別で、コントローラーまで組み立てられるか(DBには接続しない) | → Components |
| ExportSchemaUsecaseTest | 通常実行・--checkの流れ全体(--previewの有無による関数の出力の違いを含む)。Writerは本物、ファイルはメモリ上(InMemoryFileRepository)、DBは呼び出しを記録するスタブ | → 2つのUsecase |
tableusageのテスト(domain.service.tableusage・domain.model.tableusage) | 字句(SqlLexerTest・SqlLexerPropertyTest)、本体の切り出し(FunctionBodyLocatorTest。パッケージのサブプログラムの範囲)、テーブルの判定(TableReferenceScannerTest。ScanCase・ExpectedNotationで期待値を書く)、名前の解決(FunctionTableUsageAnalyzerTest)、結果の型(TableUsageTest)、精度(TableUsageMetamorphicTest・GeneratedPackagesTest・RealisticFunctionBodiesTest・FunctionTableUsageAnalyzerRobustnessTest。9.9節)。本番のDBを使わず、src/test/resources/tableusageに長い定義本体を置く | → domain.service.tableusage |
| 入口のテスト | CliArgumentsTest・CliUsageTest・DbxrayPropertiesTest・OutputDirectoryValidatorTest・TargetSelectionTest | → 入口の各クラス |
| presentationのテスト | Controller・FailureReporter(原因の併記)・DiffReportFormatter(行数の上限)・ExitStatus | → presentation |
| domain.modelのテスト | 多重度・グループ分割・関連の集合(描画距離を含む)・ビューの参照・テーブル名パターン・TableScope・観点・TableDefinitionContent・TableSnapshot等 | → domain.model |
| domain.serviceのテスト | 突き合わせ・スナップショットの書き込みと比較・unified diff・配置の規則(PathSegmentsTest)・全Writer・全テンプレート(TableDefinitionTemplatesTestは911行)・DB由来の文字列の注入(MarkdownInjectionTest) | → domain.service |
| infrastructureのテスト | 接続情報・DB種別・DTOの変換・ファイル・サイドカーYAML・パス(出力先の外を指さないことの網羅的な検査OutputPathContainmentTestを含む)・JSON | → infrastructure |
| ConfigFileTest testsupport | 設定ファイルの読み込みと、テスト用の部品(EntityFixtures・ForeignKeyFixtures・DiagramBoxesFixtures・MarkdownAssert・ログの警告を確かめるCapturedLogs) | ← 各テスト |
| SampleDatabaseExportIT OracleSampleDatabaseExportIT | 結合テスト。サンプルDBに対して全体を通し、出力をベースライン(docs/sample/*/output)とファイル単位で完全一致させる。--previewの出力は、既定のベースラインに差分のベースライン(docs/sample/*/output-preview)を重ねたものと比べる。--checkで差分が無いことも確かめる | → ExportBaseline |
| PostgresCatalogRepositoryIT OracleCatalogRepositoryIT | mapperの各SQLの取得結果(パーティション・多重度・オーバーロード・ビューの参照・メジャーバージョン・トリガーの本体等) | → 2つのmapper |
| ExportBaseline SampleDatabase OracleSampleDatabase | Testcontainersでddl.sqlを流したDBを起動し、本番と同じ2段階でコントローラーを作る(時計は固定、作成日は比較の前に置き換える) | ← 結合テスト |
| 一致するファイルはありません。 | ||
./gradlew buildは、domain配下(com/dbxray/domain/**)の単体テストのカバレッジがline 95%・branch 85%を下回ると失敗します。結合テストはDockerが必要なため、別のタスク(integrationTest・oracleIntegrationTest)に分かれています。
12変更するときの道しるべ
よくある変更ごとに、触る場所と確かめ方をまとめます。規約の原文はdocs/development/coding-guidelines.mdの「変更の種類ごとに必要な作業」にあります。
設定項目を1つ足すとき
DbxrayPropertiesのSETTINGSに項目(設定ファイルでの位置・CLI引数名・値の形式)を足し、既定値と変換を書く。上書きのCLI引数はCliArgumentsがこの表から引く。- 値をrequest(
ExportSchemaRequest・必要ならCheckDocumentDiffRequest)に足し、使う層まで同じrecordで運ぶ。文字列のまま深い層へ渡さず、入口で型にする。 - 配布する
conf/config.yml(全項目が未指定)に項目を並べ、docs/usage/cli.mdの設定の表(CLI引数の列を含む)を更新する。 DbxrayPropertiesTest・CliArgumentsTestに、未指定・誤った値・上書きのテストを足す。
DBから取得する項目を足すとき
- PostgreSQL用とOracle用の両方のmapper XMLに列を足す。別名はDTOのコンポーネント名と同じにする。
- DTO(record)にコンポーネントを足し、
toEntity()で値の形を揃える(無い値はDtoValues.textで空文字、連結された値はsplitでリスト)。業務ルール(既定値の決定・名前の生成等)はDTOやリポジトリに書かず、ドメインのメソッドに持たせる。 - エンティティに項目を足し、表示はテンプレートで(連結・エスケープ・○)。機械が読む必要があれば
…Snapshotにも足す(値の形は8.3節、「空は出力しない」は2.5節)。 ./gradlew integrationTestを実行し、PostgresCatalogRepositoryITに取得結果のテストを足す。Oracle側を変えたら./gradlew oracleIntegrationTestとOracleCatalogRepositoryIT。出力が変わるなら、verifyスキルの手順でベースラインを出し直す。
テーブル定義書にセクションを足すとき
- 中身を
TableDefinitionContent.assembleで1テーブル分に抜き出せるようにし、TableDefinitionTemplatesにセクションを返すメソッドを足して、TableDefinitionWriterの並びに加える。条件付きのセクションは空文字を返せば出力されない。 - Writerの中でMarkdownの文字列を組み立てない。ファイル名やリンクは
DocumentLocationsの規則を使う。
出力形式を足すとき
ExportSinkを実装したクラスと、それを作る…SinkFactoryをdomain.service.exportに作り、FactoryをComponentsで組み立てて、ユースケースが渡すリストに加える。--checkで比較したいかどうかで、CheckDocumentDiffUsecaseのリストに加えるかを決める。- 配置の規則は
…Locationsに、パスの解決はOutputPathResolverに足す(application層でパスの文字列を組み立てない)。
新しいドメインサービス・リポジトリを足すとき
- 依存はコンストラクタの引数で受け取る(
@Injectは付けない)。Componentsにnewを足す。DB種別で実装が変わるものや、それに依存するものはComponents#controllerの中へ。 - 組み立ての漏れ・引数の不一致はコンパイルエラーになり、両方のDB種別で組み立てられることは
ComponentsTestで分かる。ファイルを読み書きするならFileRepositoryを受け取る(Filesを直接使うとArchitectureTestが落ちる)。日付が要るならClockを受け取る。
プレビューの機能を足す・外すとき
- 足す:
PreviewFeatureにenumの値を足し、SchemaExportPipelineなどの有効かどうかで分かれる箇所だけが、request.previewFeatures()を見ます。--previewは機能を個別に選ばず全部を有効にするため、引数は増えません。出力が増えるファイルは、スナップショットに入れず、参考情報(insights/)か、--previewのときだけ出す節にします(スナップショットを変えると--checkの結果が変わるため)。 - 互換性の扱い:プレビューの機能の出力・挙動は互換性の対象外です。
docs/usage/cli.mdの「プレビューの機能」の表に足し、PRにpreviewラベルを付けます(CONTRIBUTING.md)。 - ベースライン:
--previewで変わる・増えるファイルだけを、差分のベースラインdocs/sample/{postgres,oracle}/output-previewに置きます。既定のベースラインと重複して持たず、結合テストが既定のものに重ねて比べます。サンプルの関数が変わったときも作り直します(verifyスキルの手順)。 - 外す(
--previewなしでも動くようにする):機能追加として扱い、リリースノートで知らせます。実際の業務DBでの精度の確認を、外す前に行います。
関数の定義本体を読むコードを足す・変えるとき
- 字句の規則は
SqlLexerに、テーブルの判定はTableReferenceScannerに、DBごとの切り出しは…BodyLocatorに置きます。正規表現は使いません(ArchitectureTestが検出する)。どんな入力でも例外を投げず、入力の長さに比例する時間で終えるようにします。 - 判定の規則を変えたら、
TableReferenceScannerTest・FunctionBodyLocatorTestのケースの一覧に足します。範囲の切り出し(PackageSubprograms)を変えたときは、GeneratedPackagesTestも確かめます(後ろのサブプログラムすべてに波及するため)。 - DBMSを足したら、
Dbmsのenumに足します。switchが網羅的なため、DBごとに分岐している箇所がコンパイルエラーで分かります。
スナップショットの形式を変えるとき
- スナップショットはmcp-serverとの契約です。互換性の無い変更なら
DatabaseSnapshot.FORMAT_VERSIONを上げ、mcp-serverのSnapshotDirectoryReader.SUPPORTED_FORMAT_VERSIONを追従させる。 ./gradlew :mcp-server:testを実行する(SampleSnapshotContractTestがベースラインを読む)。項目の追加は互換で、mcp-serverを変えなくてもget_table等の結果に現れます(mcp-server 読解ガイドの6章)。
確かめ方の早見表
| 変更 | 実行するもの |
|---|---|
| どの変更でも | ./gradlew build(単体テスト・ArchitectureTest・Spotless・domainのカバレッジ基準)。Javadocの書式は./gradlew javadoc(doclint) |
| mapper XML・ドメイン層・テンプレート | ./gradlew integrationTest(Docker上のPostgreSQL。出力全体をベースラインと比較) |
| Oracle用mapper、Oracleの出力が変わりうる変更 | ./gradlew oracleIntegrationTest(Oracle Database Free。メモリ2GB程度) |
| スナップショットの形式 | ./gradlew :mcp-server:test |
| 出力を意図して変えた | verifyスキル(.claude/skills/verify/SKILL.md)の手順でdocs/sample/*/outputを出し直す。--previewの出力が変わるなら、差分のベースラインoutput-previewも作り直す |
参考情報の形式(functionTableUsages.json等) | ./gradlew :mcp-server:test(SampleFunctionTableUsagesContractTestが差分のベースラインを読む)。互換性の無い変更ならFunctionTableUsagesInsight.FORMAT_VERSIONとmcp-serverのInsightsDirectoryReader.SUPPORTED_FORMAT_VERSIONを揃えて上げる |
やってはいけないこと
- 途中の層で
catchしてログを出し、再スローする(報告はFailureReporterだけが行う)。catch (Exception e)で広く包む(別の失敗まで同じ文言になり、原因も消える)。 - domain・applicationで
UserCorrectableExceptionを投げる(利用者が直せる入力の誤りは、ユースケースを呼ぶ前に入口で検証する)。 - SQLでMarkdown向けのエスケープや表示用の連結をする(スナップショットにMarkdownの記法が混ざる)。
- 判定をするドメインサービスが、警告をログへ直接出す(通知を値で返し、出力は呼び出し側に任せる)。
- ループの中で毎回
TableNamePatterns.of(…)のような値オブジェクトを組み立て直す(入口で1回組み立てて使い回す)。 - DB由来の名前を
Path.resolveやString.formatで直接パス・Markdownにつなぐ(…Locations・OutputPathResolverと、エスケープの部品を通す。10章の「DB由来の文字列を信頼しない」)。 - CLI引数・設定値・接続URLの値を、例外のメッセージやログにそのまま含める(書き誤りの引数にパスワードが入り得る)。
- 関数の定義本体を読むコードに正規表現を使う、定義本体の字句の読み取りで例外を投げる(DB由来の入力でバックトラックによる極端な遅さ・処理の中断を招く。9.9節)。
InsightWriter以外でファイルを削除する、削除の対象を広げる(ファイルの削除は、前回のfunctionTableUsages.jsonを1つ消す1箇所だけで、ArchitectureTestがInsightWriter以外のdeleteFileの呼び出しを検知する。ディレクトリの削除は--rm-distの出力先だけ)。- プレビューの機能の結果を、スナップショットに入れる(
--checkの比較対象が変わり、互換性の対象外のものを契約に混ぜることになる)。
13用語集
利用者向けのドキュメントでの呼び方とコード上の名前の対応です。全体はdomain-model.mdの用語集にあります。
- テーブルキー
TableKey。スキーマ名+物理テーブル名。テーブルの識別子で、カラム・関連・付帯情報とテーブルを結ぶ。- テーブル
TableEntity。テーブル・ビュー・マテリアライズドビューの総称(区分はTableType)。- 詳細情報
TableDetail。1テーブル分のカラム・インデックス・制約。チャンク単位で取得する。- 出力対象
ExportTargets。一括取得した軽い情報の組(条件ではなくデータ)。何を出力するかの条件は「出力対象の絞り込み条件」(TargetSelection)と呼んで区別する。- 出力対象の範囲
TableScope。設定のschema・table。- テーブル名パターン
TableNamePatterns。table=の記法(ワイルドカード・除外・スキーマ修飾)。観点の所属テーブルの指定でも使う。- 出力対象オブジェクト種別
OutputObjectType。設定のtarget.objects(trigger・function・sequence・type)。- 1テーブル分の出力内容
TableDefinitionContent。テーブル定義書1ファイル・スナップショット1行分。- 関数1つ分の出力内容
FunctionDefinitionContent。関数・プロシージャの定義書1ファイル・スナップショットと参考情報の1件分(関数+利用しているテーブル)。- プレビューの機能
PreviewFeature(--preview)。精度・出力の形を固めている途中で、付けたときだけ動く機能。互換性の対象にせず、スナップショットは変えない。- 利用しているテーブル
FunctionTableUsage・TableUsage・FunctionTableUsageAnalyzer。関数・プロシージャの定義本体から機械的に抽出した、利用している出力対象のテーブルと操作。呼び出し先の関数・動的SQLの中は含まない参考値。- 操作(C・R・U・D)
CrudOperation。利用しているテーブルへの作成・読み取り・更新・削除。TRUNCATEはD、MERGEはWHEN句の操作。- スキーマが決まらない名前
SchemaResolution・TableUsage.schemaCandidates。スキーマ修飾が無く、定義からは決まらない名前(search_path・current_user・synonym)。同じ名前の出力対象のテーブルがあるスキーマを候補として示し、決めつけない。- 動的SQL
DynamicSqlKind。実行時に組み立てるSQL(EXECUTE・EXECUTE IMMEDIATE等)。中の参照は抽出できないため、含むことだけを示す。- 字句(トークン)
SqlToken。定義本体を、空白・コメントを除いて分けた最小の単位(SqlLexerが作る)。- DBMS種別
Dbms。接続先のDBMS(PostgreSQL・Oracle)。基本情報に出す表示名と、定義本体の字句の規則の切り替えに使う。- 関連
ForeignKeyEntity・ForeignKeys。外部キー(物理、RelationType.PHYSICAL)と論理リレーション(論理、LOGICAL)の総称。クラス名は歴史的経緯でForeignKey。- 論理リレーション
- DBに制約が無く、サイドカーYAMLの
relationsで宣言した関連。ER図では破線。 - 被参照の関連
incomingRelations・ForeignKeys.referencingTo。自分を参照している関連(自己参照を除く)。- 多重度
Cardinality。参照元の外部キー列の一意性とNOT NULLから決まる、関連の両端の件数の関係。- 描画距離
output.erDiagramDistance・DiagramNeighborhood。テーブル定義書のER図に描く関連の段数。1は自テーブルの関連だけ。図が上限を超える場合は縮める。- ビューが参照するテーブル/参照しているビュー
ViewReferenceEntity・ViewReferences。ビュー・マテリアライズドビューが参照するテーブル(referencedTables)と、その逆向き(referencingViews。MCPではreferencedByViews)。DBの依存関係から取得する。- ノード
- ER図に描くテーブル。関連を持つテーブルだけを数える。
- グループ
ForeignKeyGroup。1枚のER図に描く関連のまとまり。上限を超えると連結成分ごとにまとめ直す(ForeignKeyGroups)。- 関連カラム
- 関連の参照元・参照先として使われるカラム。ER図の箱(
DiagramBoxes)に表示する。 - サイドカー
Sidecar。DBに無い情報を書くYAML(設定のキーはannotations、コード上はsidecarPath)。手動付帯情報・論理リレーション・観点を持つ。- 手動付帯情報
Annotations・TableAnnotation。テーブル説明・テーブル備考・カラム備考。- 集計(metrics)
- DB単位のREADMEに載せる、出力対象のスキーマごとの数(オブジェクト・論理名の記述状況・関連と読み解きの状況)。出力したドキュメントと数が合うよう、SQLでは数えず取得した結果から数える。
- 孤児付帯情報
- 実在しないテーブル・カラムに対する付帯情報。リネーム・削除の見落としとして警告する。
- 観点
Viewpoint。業務ドメイン別にテーブルをまとめる切り口。観点ページと観点一覧になり、スナップショットには含めない。- 突き合わせの通知
ConsistencyNotice。出力対象と関連・付帯情報・観点を突き合わせた結果。重要度に応じてログへ出す。- 基本情報
BaseInfoEntity。各ドキュメントの先頭に載せるRDBMS(メジャーバージョンを添える)・DB名・作成日。作成日はDBではなくClockから。- チャンク
- 詳細情報をまとめて取得・出力するテーブルの塊。大きさは設定の
chunkSize(既定3000、0以下でスキーマ全体)。 - Sink
ExportSink。取得した情報を1つの出力形式で書き出すもの。Markdown・スナップショット・参考情報の3つ。- スナップショット
domain.model.snapshot。取得結果を構造化したJSON Lines。生成日を含まず、--checkの比較対象で、mcp-serverの入力。- 参考情報(insights)
domain.model.insight。スナップショットの事実とは別にAIへ渡す情報(観点と、--previewのときの関数の利用しているテーブル)。--checkの対象外。- 利用者が直せる誤り
UserCorrectableException。設定・入力・実行環境を見直せば解消する失敗。報告にスタックトレースを含めない。