ユーザーマニュアル · Python 3.9+

erdscope

データベース、アプリケーションコード、設定ファイル。手元にあるものを組み合わせ、探索できる1つのスキーマへ変換します。

$ pip install erdscope && erdscope demo
データベース、アプリケーションコード、設定ファイルをerdscopeで統合し、インタラクティブHTMLとExcelワークブックを生成
データベースMySQL · PostgreSQL · SQLite
アプリケーションコードRails · Prisma · Django · SQLAlchemy · Laravel
設定ファイルJSON · YAML
↓ 1つでも、組み合わせてもOK ↓
erdscopeインタラクティブHTML · オプションのExcel定義書

目的から選ぶ

erdscopeの仕組み

erdscope は、インタラクティブな ER 図と、設計意図を記録したスキーマ定義を生成します。 稼働中の MySQL・PostgreSQL・SQLite データベースから自己完結型のインタラクティブな ER 図と、 必要に応じてExcel テーブル定義書を生成します。単一の Python ファイル (erd.py、インストール不要)として配布され、必須の依存関係はゼロです。 設定ファイルのnotes:を使えば、ER 図を「設計メモ付きのスキーマ」に 変えられます — 設計判断・運用ルール・ADR へのリンクをテーブルや関連に紐付け、実際のスキーマに対して 検証されるので、存在しない対象を指すメモは作れません。設定ファイルの groups:を使えば、関連するテーブルの集まりを角丸のタイトル付き枠で 囲めます — 純粋に見た目だけの機能で、タイトルをドラッグしてまとめて移動できます。

入力ソースは3種類あり、いずれか1つがあれば生成できます — データベースは必須では なくなりました:

これらは データベース → コード → 設定 の順にマージされ、後のレイヤーが前を補正します。 物理的な事実(カラム型・インデックス・主キー)はデータベースが優先され、関連や論理名はコード・設定が 優先、設定が常に最終決定権を持ちます。データベース URL がなければ、接続もパスワード入力も行いません。

ライブデモを試す → — コメント・インデックス・実際の FK・推測された関連を含む小さな EC サイトのスキーマです。 デモの内容はすべて単一の自己完結型 HTML ファイルであり、あなた自身の図も同じ方法で生成されます。

users・orders・products などのテーブルとその関連を示す erdscope の ER 図

データベースの真実

テーブル、カラム(完全な SQL 型、デフォルト値、extra 属性)、コメント、インデックス、実際の FK 制約 — すべてデータベースから直接読み取ります。

コード側のセマンティクスを重ね合わせ

--models は Rails / Prisma / Django / SQLAlchemy / Laravel の関連をマージします。association ですでにカバーされている DB FK は重複排除され、残りには「DB FK」バッジが付きます。

インタラクティブな探索

深度と方向を指定したフォーカス、2段階の非表示、テーブル・カラム検索、名前付きビュー、共有リンク。

読みやすいレイアウト

ビューポートを考慮した自動配置、ドラッグ時のスナップ、複数選択での整列/分布、Auto-tidy、undo/redo。

エクスポート

PNG、SVG、Mermaid、PlantUML、DBML — それぞれコピーとダウンロードに対応 — に加えて Excel ワークブック。

論理名

テーブルの DB コメントは検索可能な「論理名」を兼ね、物理名と並べて表示されます。

設計メモ(Notes)

設計判断・運用ルール・ADR へのリンクをテーブル・関連・図全体に紐付け。実際のスキーマに対して検証され、テーブル/カラムと同様に検索できます。

クイックスタート #

まだ手元にデータベースがない場合は、erdscope demo が便利です。小さなサンプル e コマーススキーマの SQLite データベースを一時ディレクトリに作成し、図を生成してブラウザで 開きます — ダウンロードも準備も不要です。

pip install erdscope
erdscope demo

内部では通常のパイプラインをそのまま実行するため、このページの他のフラグもすべて併用できます (erdscope demo --excel defs.xlsxerdscope demo --only 'order*' など)。 ブラウザを自動で開きたくない場合は --no-open を付けてください。全オプションの一覧は 下のCLIリファレンスを参照してください。

erdscope mysql://readonly@127.0.0.1:3306/myapp_production -o erd.html

# アプリケーションコードから解析した関連セマンティクスで拡張(任意)
erdscope mysql://readonly@127.0.0.1:3306/myapp_production \
        --models /path/to/rails/app -o erd.html

# テーブル定義書も同時に出力
erdscope mysql://readonly@127.0.0.1:3306/myapp_production \
        --excel table_definitions.xlsx -o erd.html

# PostgreSQL の場合も同じ — スキーマはデフォルトで public、?schema=name で上書き可能
erdscope postgres://readonly@127.0.0.1:5432/myapp_production -o erd.html

# SQLite: ファイルを指すだけ — サーバー不要、インストール不要
erdscope sqlite:///path/to/app.db -o erd.html

生成された erd.html を任意のブラウザで開いてください — すべてが1ファイルに内包されている ので、メールで送ったり、Wiki にコミットしたり、共有ドライブに置いたりできます。

パスワードの扱い

読み取り専用の DB アカウントを使い、接続 URL にパスワードを書かないでください — シェル履歴に残ってしまいます。erdscope は次の順序でパスワードを解決します。

  1. URL に埋め込まれたパスワード(mysql://user:pass@host/db)— 実運用では避けてください。
  2. MYSQL_PWD(MySQL)/ PGPASSWORD(PostgreSQL)環境変数(設定されている場合)。
  3. それ以外で、標準入力が対話的な端末であれば、隠し入力のパスワードプロンプト (argv にもシェル履歴にも残りません)。

sqlite:// はローカルファイルを読むだけなので、アカウントもパスワードも不要です — 上記はネットワーク接続のエンジンにのみ当てはまります。

非対話実行の場合(CI・スクリプト・パイプ) erdscope は標準入力が TTY のときだけプロンプトを出します。スクリプトや CI ジョブの中ではプロンプトを 完全にスキップし、ブロックすることなく、手元にあるパスワード(無い場合もあります)でそのまま接続を試みます。 それが間違っていれば通常の接続エラーメッセージが出るだけで、ハングすることはありません。 user:@host/db(明示的な空パスワード)と書いた場合も「パスワードが指定された」扱いになり、 プロンプトはスキップされます。PostgreSQL の場合、プロンプトに空行で答えると PGPASSWORD は 未設定のままになるため、libpq 自身の ~/.pgpass 参照がそのまま働きます。

踏み台サーバー / SSH トンネル経由

ssh -N -L 3307:db-host:3306 bastion &
erdscope mysql://readonly@127.0.0.1:3307/myapp_production -o erd.html

インストール・必要要件 #

PyPI からインストールすると erdscope コマンドが使えるようになります。

pip install erdscope        # または: pipx install erdscope
erdscope mysql://readonly@127.0.0.1:3306/myapp -o erd.html

何もインストールしたくない場合は、erd.py が依存ゼロの単一ファイルなので、ダウンロードして任意の Python 3.9 以降で実行できます。このマニュアル全体で 使っている erdscope コマンドと完全に同じ動作です。

curl -O https://raw.githubusercontent.com/orapli/erdscope/main/erd.py
python3 erd.py mysql://readonly@127.0.0.1:3306/myapp -o erd.html

必要要件は厳密には Python 3.9 以降のみです。ツールをより使いやすくする2つのライブラリが ありますが、どちらも完全にオプションです — インストールされていなくても erdscope は正しくフォールバック して動作します。pip でインストールした場合は extras でまとめて導入できます (pip install 'erdscope[mysql]' で PyMySQL、'erdscope[postgres]' で psycopg、 'erdscope[yaml]' で PyYAML、'erdscope[all]' で全部)。

ライブラリ用途未インストールの場合
PyMySQLMySQL 接続mysql CLI(PATH 上にある必要あり)へのシェルアウトにフォールバック — トラブルシューティング参照
psycopg(または psycopg2)PostgreSQL 接続psql CLI(PATH 上にある必要あり)へのシェルアウトにフォールバック — トラブルシューティング参照
(なし)SQLite 接続(sqlite:///file.dbPython 標準ライブラリの sqlite3 モジュールを使用 — 常に利用可能で、インストールもフォールバックも不要
PyYAML.yml/.yaml 設定ファイルの読み込み.json 設定ファイルであれば依存関係なしで動作します。PyYAML がない状態で .yml/.yaml を指定した場合は、明確なエラーで終了します

--excel 出力には上記のどれも不要です — スプレッドシート用パッケージではなく、 Python 標準ライブラリの zipfile/XML 処理を使って直接書き出しています。

もう2つのライブラリはテストスイート専用で、実行時には一切使われません: openpyxl(ユニットテストで --excel 出力をラウンドトリップ検証)と Playwright(ブラウザ E2E スイートで生成 HTML を操作)です。 どちらも図の生成・閲覧には不要です。

検証済みバージョン #

erdscope が解析する入力形式はめったに変わらないため、正確なバージョンは見かけほど重要では ありません — より新しいリリースでも動作し続けることが期待できます(ただし、テスト済みの構文を 超えるものは、解析されずに無視されることがあります)。参考までに、各入力が実際に検証されている バージョンは次のとおりです:

入力検証対象
MySQL8.4 — CI 上の実サーバー統合テスト(information_schema)。開発は 8.x 系に対して実施
PostgreSQL16 — CI 上の実サーバー統合テスト(pg_catalog/information_schema
SQLiteCPython 同梱の sqlite3 モジュール(サポート対象の 3.x すべて)
Rails schema.rbRails 7.x / 8.x が出力する形式(ActiveRecord::Schema[7.x]、および旧来のバージョン表記なしヘッダー)
Rails モデルRails 7.x 時点のアソシエーション DSL — has_many/has_one/belongs_to/has_and_belongs_to_manythrough:polymorphic:、STI、concern、独自基底クラス。Mastodon の実コードベースでも動作確認済み。動的に計算される定義(および structure.sql)は対象外
PrismaPrisma 5 / 6 時点のスキーマ言語 — @map/@@map、enum、名前付きリレーション、暗黙・明示の多対多、自己参照、複合 @@id/@@unique@@schema
DjangoDjango 4.2 / 5.x 時点のモデル — FK / OneToOne / M2M(through= 含む)、抽象基底、db_table/db_columnGenericForeignKey(ポリモーフィックのマーカーとして保持)。swappable な AUTH_USER_MODEL への FK はカラムを保持しエッジのみスキップ
DBML(sources[].type: dbmldbml.dbdiagram.io に記載の構文 — Table/カラム設定(pk/not null/unique/increment/default/note/インライン ref)、indexes { (a, b) [pk] } による複合主キー、独立文・ブロック双方の Ref(4種類のシンボルすべて)、Enum(認識はするが素通り)。TableGroup・独立した Note オブジェクト・複合(複数カラム)Ref は今回のスコープ外 — 型付き入力ソース参照
Mermaid erDiagramsources[].type: mermaid.ererDiagram 構文 — エンティティブロック(PK/FK/UK、クォート付きコメント)とリレーション行(実線・破線を問わずcrow's-foot記法の全組み合わせ)。単体の .mmd/.mermaid ファイルのみ対応 — Markdown フェンス抽出は未対応
SQLAlchemy(sources[].type: sqlalchemy.models従来型 declarative_base()・2.0 DeclarativeBase 両スタイルの宣言的モデル — 型オブジェクト明示の Column(...)/mapped_column(...)ForeignKeyrelationship(secondary=...) の多対多。静的 AST 解析のみ(モデルを import しない)。2.0 の型注釈のみのカラム・関連は Mapped[...] から読み取り(型・null 許可・対象クラス・多重度)
Laravel Eloquent(sources[].type: laravel.models*.php モデルファイル(vendor/ 除外)の hasMany/hasOne/belongsTo/belongsToManymorphTo/morphMany/morphOne/morphToMany。関連のみ — カラムはライブDBか別の物理ソースから
Python3.9 以上(requires-python)。CI は最新の CPython 3.x で実行

設定ファイル #

フラグの一覧が長くなってきたら、代わりに設定ファイルにまとめましょう。ツールを実行する場所に .erdscope.json(PyYAML がインストールされていれば .erdscope.yml/.yamlも可) を置くと自動的に読み込まれます — --config は不要です。ほとんどのキーは上記の CLI オプション に対応します(snake_case)。engine/host/port/user/database (DB 接続情報 — engine はデフォルトの "mysql""postgres" (デフォルトポートも 5432 に切り替わります)、または "sqlite" を指定します)、 relations(手動 FK 宣言)、sourcesrails.schema を含む、型を明示した入力ソース宣言)は設定ファイル専用で、対応する CLI オプションはありません。 engine: "sqlite" の場合、database はデータベース名ではなくローカルファイルパスで、 host/port/user は指定できません:

{
  "engine": "sqlite",
  "database": "db/app.db"
}
{
  "host": "127.0.0.1",
  "port": 3306,
  "user": "readonly",
  "database": "myapp_production",

  "output": "erd.html",
  "models": "../myapp",
  "max_rows": 15,
  "infer_fk": true,
  "only": ["user*", "post*"],
  "table_map": { "Widget": "crm_widgets" },

  "relations": [
    { "table": "orders", "column": "buyer_code", "references": "users" },
    { "table": "profiles", "column": "person_ref", "references": "users",
      "one_to_one": true, "name": "owner" }
  ]
}

明示的な CLI フラグは常に同名の設定キーより優先され、値を完全に置き換えます(only のような リスト値も設定ファイルの内容とマージされることはありません)。意図的に password/url キーは存在しません — host/port/user/database が 別々のフィールドになっているのは、パスワードを書き込める場所をそもそも作らないためです。CLI と同じように MYSQL_PWD/PGPASSWORD~/.my.cnf/~/.pgpass、または 対話プロンプトで指定してください。

すべてのキーを説明した完全な注釈付きサンプルは erdscope.example.yml (ライブデモのスキーマがベース)を参照してください。

型を明示した入力ソース(sources:

--models/設定の models は、パスがどんな種類のプロジェクトかを自動判別します (Rails の app/models ディレクトリ、schema.prisma、Django プロジェクト、 Laravel アプリ、SQLAlchemy モデルのディレクトリ — パスがファイルの場合、db/schema.rb (Rails の静的スキーマダンプ)であることも判別します)。 設定ファイル専用の sources: キーは、これに代わる型指定の方法です — { id, type, path } オブジェクトのリストで、各エントリが自分の type を名乗るため判別が一切不要になります。 models とは独立していて、両方指定すればどちらも適用されます(順序は sources (宣言順)→models、後のものが同順位の対立で優先されるのは今までと同じです)。

version: 1
sources:
  - id: schema
    type: rails.schema
    path: db/schema.rb
  - id: app
    type: rails.models
    path: app/models

任意のトップレベルキー version: 1 は、設定ファイルの形式マーカーです — 現時点で有効な値は これだけで、他に実行時の効果はありません。将来の設定形式の変更に備えたものです。

登録済みのフレームワークオーバーレイはすべて、<name>.models という型を自動的に持ちます (rails.modelsprisma.modelsdjango.modelssqlalchemy.modelslaravel.models、および --adapter で登録した任意のオーバーレイ自身の型も)— 自動判別と同じパーサーを、判別のステップ なしで直接呼び出します。type がレジストリに存在するかどうかは、そのソースが実際に実行される 時点までチェックされないため、設定を読み込む時点ではまだ存在しない --adapter プラグインが 登録する型も有効に扱われます。

型を指定したソースが何も解析できなかった場合 — たとえば Prisma プロジェクトを誤って rails.models として宣言した場合 — は、ソースの id とその型が期待するレイアウトを 明示したハードエラーになります。黙って空の図が生成されることはありません。空の結果が本当に意図した ものである場合(たとえば作成直後でまだ空の app/models)は、ソースごとに allow_empty: true で明示的にオプトインしてください。rails.project エントリはこのフラグを展開後の両方のハーフに引き継ぎます。

rails.schema は新しい型です。Rails の db/schema.rb ファイル — Rails アプリのデータベーススキーマを表す、自動生成された正規のダンプ — を、テキスト解析だけで カラム・インデックス・主キー・外部キーへと静的に解析します。Ruby は一切実行しません。その ため Rails ランタイムも gem のインストールも実データベースも不要で、チェックインされた schema.rb ファイルさえあれば動きます。パーサーが認識できないもの(動的なデフォルト値、 サポート外のカラム型、認識できない文)は標準エラー出力への警告として報告され、サイレントに無視される ことはありません。パーサーが見つけた外部キーは、schema FK という来歴を持つ association になります — エッジの種類を参照してください。

rails.project は Rails アプリのルート全体を指すマクロです。 rails.schema<path>/db/schema.rb)と rails.models<path>/app/models)の両方に展開されます(存在する方だけ)。片方しか存在しない プロジェクトでも、どちらを省略したかを知らせる注記が標準エラー出力に出た上で動作します。どちらも 存在しない場合はハードエラーになります:

sources:
  - id: app
    type: rails.project
    path: ../myapp

新しい schema レイヤーの追加により優先順位も拡張されます。物理的な事実 (カラム型、インデックス、主キー)は 設定 > 実データベース > rails.schema > models の順です — schema.rb のダンプはコード解析よりも実データベースに近い情報源ですが、実データベースへ の接続がある場合はそちらが優先されます。association/コメント(論理名)は 設定 > models > rails.schema > DB の順です — 宣言された association は 依然として機械的に導出されたものより優先されますが、schema.rb から解析された外部キーは、 生の DB FK の機械的な名前よりも優先されます。schema.rb 由来の外部キーをモデルの belongs_to も宣言している場合は、1本のエッジにマージされ(モデル側の名前が採用され、来歴は declared になります)— これは実 DB FK に対してすでに行われているのと同じ調停です。

dbmlDBML ファイルをテキスト解析だけで静的に解析します — テーブル・カラム・ インデックス・主キー(indexes { (a, b) [pk] } エントリによる複合主キーも含む)、そして Ref リレーション(>/</-/ <> の4種類のシンボルすべて、カラムへのインライン記法・独立文・ブロック記法の いずれも)です。DBML ライブラリへの依存はありません。rails.schema と同じ優先ランクを 持ちます(どちらも「宣言された物理スキーマ文書」という点で同じ性質のため)— そのため 設定 > 実データベース > {rails.schema, dbml} > models / 設定 > models > {rails.schema, dbml} > DB という同じ優先順位が適用され、 Ref から解析された外部キーは rails.schema と同じ schema FK という来歴を持ちます。今回のスコープ外: TableGroup、独立した Note オブジェクト、複合(複数カラム)Ref — いずれも認識はしますが、ファイル名と行番号付きの 警告とともにスキップされ、サイレントに無視されることはありません。EnumProject ブロックは認識・消費されますが効果はありません(enum のメンバー一覧を保持する 場所が erdscope のスキーマにはなく、ソース側が示唆するタイトルを読む仕組みもまだないためです)。

mermaid.er は Mermaid の erDiagram をテキスト解析だけで 静的に解析します — エンティティブロック(カラム、PK/UK マーカー、任意の クォート付きコメント。FK は表示上のヒントに過ぎず、それ単体では IR に影響しません)と リレーション行(crow's-foot 記法のカーディナリティを belongs_to/has_one/ has_and_belongs_to_many に変換し、ラベルが association の名前になります)です。DBML と 異なり、リレーション行はカラムを一切指定しないため、Mermaid 由来の association には foreign_key が付きません。これは最も優先順位の低い入力ソースです — Mermaid のカラム型はスケッチしながら書き留めた自由なテキストにすぎないため、mermaid.er は実データベース・rails.schema/dbml--models によるコード解析の いずれに対しても、物理・論理どちらの優先順位においても勝つことはありません — 他のどのソースも 供給しなかった情報を補うだけです。リレーション行にしか登場せず { } ブロックを一度も 持たないエンティティも、カラムを持たないテーブルとして生成されます。MVP のスコープ: 単体の .mmd/.mermaid ファイルのみ — Markdown フェンスからの抽出は未対応です。

sqlalchemy.models は SQLAlchemy の宣言的モデル — 単一の .py ファイル、またはディレクトリ(再帰。venv/migrations/tests 系ディレクトリは除外)— を AST 解析だけで読み取ります: import も実行もせず、SQLAlchemy への依存もありません。 従来型の declarative_base() スタイルと 2.0 の DeclarativeBase スタイルの 両方を認識します。型オブジェクトを明示した Column(...)/mapped_column(...) は erdscope の粗い型に写像され、ForeignKey('table.col')belongs_to (カラムが unique なら has_one)に、relationship(secondary=...) は 多対多になります。自動判別は他フレームワークのマーカーファイル方式と異なり、内容ベース(再帰スキャン)で SQLAlchemy プロジェクトを認識します。

2.0 の型注釈中心のスタイルは、注釈そのものから読み取ります(隣の呼び出しに型も対象も書かれないことが 多いため)。id: Mapped[int] = mapped_column(primary_key=True)Mapped[int] からカラム型を決定し、 posts: Mapped[list["Post"]] = relationship() は第一引数なしでも Mapped[list["Post"]] から対象クラスを解決します。null 許可も 2.0 の規則に従い、 Mapped[str] は NOT NULL、Mapped[Optional[str]]Mapped[str | None] は nullable になります(明示的な nullable= キーワードや 型引数があればそちらが常に優先)。多重度も注釈から決まります — Mapped[list["Post"]](および WriteOnlyMapped/DynamicMapped)は to-many、スカラーの Mapped["User"] は to-one です。静的解析で解決できないもの: 本物のユニオン(Mapped[int | str])や未知のジェネリック (Mapped[dict[str, Any]])は型が空のままになり、カラム側のコレクション注釈 (Mapped[list[int]] — ARRAY/JSON カラム)は要素型で報告せず空のままにします。

laravel.models は Laravel Eloquent モデルの *.php ファイルが並ぶディレクトリ(通常 app/Modelsvendor/ は常に除外)を、 コメント除去済みソースへの正規表現解析で読み取ります — rails.schema と同じ流儀で、 PHP ランタイム不要・入力を決して実行しませんhasMany/hasOne/belongsTo/belongsToMany (ピボットテーブルは through として保持)と morph* 系(ポリモーフィック)が 関連になります。Rails モデルと同様に関連のみ/DB-first のソースです: Eloquent モデルはカラムを宣言しないため、カラム層はライブDB(または別の物理ソース)と 組み合わせてください。解決できない関連ターゲットは file:line 付き警告になり、 黙って落ちることはありません。

手動での関連宣言

relations は、他のどのソース(実際の FK 制約、*_id による名前推測、 --models によるコード解析)でも見つけられない関連を宣言します — 命名が特殊なカラムや、 gem 提供の concern・動的な association によって静的解析から隠れているものなどです。 --models をまったく使わなくても単独で機能し、設定ファイルだけで完全な関連グラフを 組み立てることもできます。優先順位はコード解析による association と同じです — --infer-fk より先に適用される(そのため該当カラムに対する誤った名前ベースの推測も抑制します)ほか、同じカラムに 対する実際の DB FK 制約よりも優先されます。relations 内の未知のテーブル/カラム/参照先は 常にタイプミスとみなされ、サイレントに無視されるのではなく、必ずハードエラーになります。

スキーマ入力ソースとしての設定(tables:

設定値や relations に加えて、設定ファイルには それ自体が完全な入力ソースとなる tables: セクションを書けます — データベースと --models に並ぶ第3のレイヤーです。 データベースもコードもなしに図を生成でき、最優先レイヤーとしてマージされるため、他のソースの結果を 補正することもできます。未対応の DB やフレームワークを手書きで記述したり、実在するものを 修正・補完する用途に使えます。

ゼロから宣言する場合 — tables は「テーブル名 → 定義」のマップです:

title: billing
tables:
  customers:
    comment: 顧客アカウント
    primary_key: id
    columns:
      - { name: id,    type: bigint,  primary: true }
      - { name: email, type: varchar, nullable: false, comment: ログインアドレス }
    indexes:
      - { name: idx_customers_email, columns: [email], unique: true }
    associations:
      - { type: has_many, name: invoices, target: invoices }
  invoices:
    columns:
      - { name: id,          type: bigint, primary: true }
      - { name: customer_id, type: bigint }
    associations:
      - { type: belongs_to, name: customer, target: customers, foreign_key: customer_id }

テーブル配下の項目はすべて任意です(フィールドを省略すると「変更しない」=下位レイヤーの値を維持)。 primary_key は単一カラムでも、複合キー用のリストでも指定できます。複合外部キーは未対応のため foreign_key は単一カラムです。title(トップレベル)は、名前を取得できるデータベースが ない場合にワークブック/図の名前になります(優先順: title → データベース名 → フレームワークの プロジェクト名 → 出力ファイル名)。

ゼロから宣言する代わりに、DB/コードの結果を補正することもできます — 属性の上書き、カラムやテーブルの 削除、リストの全置換:

tables:
  orders:
    columns:
      - { name: status,      comment: 受注ステータス }   # この属性だけを上書き
      - { name: legacy_flag, drop: true }               # カラムを削除
    associations:
      - { type: belongs_to, target: users, foreign_key: created_by_id, drop: true }  # 関連を削除
  temp_scratch:
    drop: true                                          # テーブルごと削除
  reports:
    columns_mode: replace                               # 下位レイヤーのカラムを破棄してから適用
    columns:
      - { name: id,   type: bigint, primary: true }
      - { name: body, type: text }

既定は「追加・上書き」マージです。明示的な操作は drop: true(テーブル・カラム・インデックス・ 関連に指定)と columns_mode/indexes_mode/associations_mode: replace (そのテーブルの当該リストを下位レイヤー分ごと破棄してから設定分を適用)です。インデックスには name が必要で、関連の削除は identity(外部キー列と参照先、非 FK 関連では加えて name)で特定します。

優先順位: 物理的な事実(カラム型・インデックス・主キー)はデータベース由来で、設定が上書き 可能。関連とコメント(論理名)はコード・設定がデータベースより優先。設定レイヤーは常にデータベースと コードの両方に勝ちます。

検証は2段階です。構文は設定の読み込み時にチェックされ、未知のキー(ネストした綴り間違いも 含む)、不正な型、不正な操作は事前に拒否されます。参照は実行時(全ソースのマージ後)にチェックされ、 実在しないテーブル・カラムを指す dropforeign_key/target は ハードエラーになります。タイプミスが黙って誤った図を生むことはありません。

設定値の型に注意 キーは名前だけでなく、erdscope 実行前に型もチェックされます。only/exclude は 単なる文字列ではなく文字列のリストである必要があります(文字列のままだと1パターンとしてではなく 文字単位でマッチしてしまいます)。infer_fk は文字列の "true"/"false" ではなく、真の JSON boolean である必要があります。型が一致しない場合、以前は図がサイレントに間違った ものになっていましたが、現在は明確なエラーとして事前に検出されます — エラーに遭遇したら、上の例と 照らし合わせて値の型を確認してください。

設計メモ(Notes): 図に設計判断を紐付ける #

設定ファイルの notes: は、設計判断・運用ルール・ADR へのリンクといった短いプレーンテキストの メモを、テーブル・特定の関連・図全体のいずれかに紐付けます。notes は読み取り専用のサイドカーです — カラム・関連・マージの優先順位、どのソースが「勝つか」には一切影響しません。すでに確定したスキーマに 注釈を付けるだけです。

notes:
  - id: user-retention
    target: { type: table, table: users }
    title: 保持ポリシー
    text: 退会済みアカウントは1年間保持後、匿名化する。
    links:
      - { label: ADR-004, url: https://example.com/adr/004 }

  - id: order-ownership
    target: { type: relation, source_table: orders, target_table: users, foreign_key: user_id }
    text: ユーザー匿名化後も注文は保持する(会計上の記録保持のため)。

  - id: diagram-conventions
    target: { type: global }
    title: この図の読み方
    text: 破線の琥珀色エッジは推測された関連であり、実際の FK ではない。

すべてのメモには設定ファイル内で一意な id と、空でない text が必要です。 titlelinks は任意です。links[] の各要素は { label, url }urlhttp:// または https:// で始まる必要があり、それ以外(javascript:data:、スキームのない文字列など)は 設定の読み込み時点で拒否され、図に届くことはありません。

target.type は次のいずれかです:

検証は2段階です。上の tables: と同様に、構文は設定の読み込み時にチェックされます (未知のキー、不正な型、不正な target、http(s) 以外のリンク URL)。対象が実在するかどうかは 全ソースがマージされたあとにチェックされます — DB、--models、設定 tables:(それ自身の追加・削除も含む)がすべて解決された最終スキーマに対してです。そのため メモは tables: 自身が追加したテーブルや関連を対象にでき、逆に tables:削除した対象を指すメモは、メモの id を含む正しいハードエラーになります。

メモはプレーンな、HTML エスケープ済みのテキストのみとして描画されます — Markdown も 生 HTML もスクリプトもありません — テーブルの詳細パネル、Associations 一覧内の該当関連の隣、または global メモなら凡例に表示されます。検索対象にもなりますが、2つの検索ボックスには対象範囲に 小さな違いがあります: 左ペインのフィルタは table・relation・global のすべてのメモに一致し、 global の一致は専用のバナー行として表示されます(global メモにはバッジを 付けるテーブルがないためです)。ツールバーのハイライトは図のノードに印を付ける仕組みのため、 テーブル名・カラム名・コメントに加えて table/relation メモ(それを持つテーブル側)には一致しますが、 global メモには一致しません — こちらは左ペインのフィルタで見つけてください。いずれの場合も、 一致した行はテーブル/カラムへの一致と見分けられるよう区別して表示されます。

右ペインの orders 詳細パネル。belongs_to :user 関連の直下に「Customer retention」のリレーションメモ、下部の NOTES セクションに「Order state machine」のテーブルメモが表示されている

ライブデモの orders 詳細パネル — 関連メモ(Customer retention)は該当する関連の直下に、テーブルメモ(Order state machine)は専用の Notes セクションに表示されます。

テーブル/関連のメモは、その対象が実際に見えている場合にのみ表示されます: テーブルのチェックが外れて いたり🚫で完全非表示にされている場合、その詳細パネル自体が開かれないため、メモもどこにも表示されません — 非表示テーブルのメモを無理に見せる仕組みはありません。global メモの凡例エントリは それとは別の、常時利用可能なブロックであり、どのテーブルが表示されているかの影響を受けません。

Excel にも反映されます notes が1つでも設定されていれば、--excel ワークブックに Notes シートが 追加されます — シートの内容はExcel ワークブックを参照してください。

グループ(Groups): 関連するテーブルを枠で囲む #

設定ファイルの groups: は、関連するテーブルの集まりを図の中で角丸のタイトル付き枠で 囲みます — 「Billing」「Orders」のようなドメインを、スキーマ自体には手を加えずに視覚的に示す軽量な 手段です。notes: と同様、groups も読み取り専用のサイドカーです — カラム・関連・マージの優先順位・レイアウトには一切影響しません。枠はメンバーのテーブルがすでに 配置されている場所の周りに、そのまま描画されるだけです。

groups:
  - id: billing
    title: 請求
    tables: [invoices, payments, coupons]
    color: "#0d9488"

  - id: catalog
    tables: [products, categories, product_categories]
角丸の枠と Catalog というタイトルチップで囲まれた products・product_categories・categories の3テーブル

上の catalog グループの描画結果(ライブデモより) — メンバーの周りに角丸の枠とタイトルチップが描かれます。チップをドラッグするとメンバー全員がまとめて動きます。

すべてのグループには設定ファイル内で一意な id と、空でない tables リストが必要です。title(省略時は id が使われます)と color は任意です。color は hex 文字列(#0d9488#0d9 など)である 必要があります — それ以外は設定の読み込み時点で拒否されます。これは notes: のリンク URL が http(s) のみに限定されているのと同じ、第一防衛線としての考え方です。

1つのテーブルが所属できるグループは最大1つです。同じテーブルを2つの異なるグループが 主張した場合は、両方のグループ id とそのテーブル名を含むハードエラーになります — サイレントにどちらか一方が勝つことはありません。今回のリリースでは、グループの重なりやネストは サポートしていません。

検証は2段階です。上の notes:/tables: と同様に、構文は 設定の読み込み時にチェックされます(未知のキー、不正な型、不正な color)。各メンバー テーブルが実在するかどうか、および重複所属のチェックは、全ソースがマージされたあとに 行われます。そのためグループは tables: 自身が追加したテーブルを対象にでき、逆に tables:削除した対象を指すグループは、グループの id を 含む正しいハードエラーになります。

ビューアでは、グループの枠は図の最背面に描画され(ノードとエッジは常にその上に描かれるため、 テーブルのクリックやドラッグを妨げることはありません)、メンバーの移動に合わせてリアルタイムに 追従します。グループのタイトルチップをドラッグすると、メンバー全員を一度にまとめて移動できます。 ツールバーの「Groups」トグルで全ての枠をまとめて表示/非表示にできます — 設定に groups: が無い場合はこのトグル自体が表示されないため、この機能導入前と全く同じツールバーに見えます。PNG・SVG のいずれのエクスポートも、表示中の枠を含みます。

--only/--exclude は、グループの tables をフィルタ後に 生き残ったテーブルだけに絞り込みます。生き残ったメンバーが0件になったグループは、対象から 外れていたメモと同様、出力から完全に取り除かれます。

Excel にも反映されます groups が1つでも設定されていれば、--excel ワークブックに Groups シートが 追加され、概要シートには各テーブルの所属を示す Group 列が加わります — 詳細はExcel ワークブックを参照してください。

CLIリファレンス #

この表は erd.pyargparse 定義に基づいて検証済みです — 手元のバージョンと 一致しているか、いつでも erdscope --help を実行して確認できます。

erdscope [mysql://user@host/db | postgres://user@host/db | sqlite:///file.db | demo] [options]
引数説明
mysql://…postgres://…、または sqlite:///…位置引数。データベース接続 URL。postgres:// は任意の ?schema=name(デフォルト public)を受け付けます。postgresql:// という表記も使えます。sqlite:///path/to/app.db は標準ライブラリの sqlite3 でローカルファイルを読みます(サーバー不要・インストール不要)。MySQL/PostgreSQL/SQLite は設定ファイルの engine/host/port/user/database から組み立てることもできます(パスワード用のフィールドはありません。engine: sqlite の場合 database はファイルパスで host/port/user は指定できません — 設定ファイル参照)。
demo位置引数。実際のデータベースの代わりに、同梱のサンプル e コマースデータベースから生成します — 一時ディレクトリに使い捨ての SQLite コピーを作成し、通常のパイプラインを実行して、結果をブラウザで開きます。この表の他のオプションもすべて併用できます。設定ファイルの自動検出は強制的に無効化され、明示的な --config 指定も警告付きで無視されるため、カレントディレクトリの状態に関わらずデモは常に同じ結果になります。デフォルトの出力先は(erd.html ではなく)erd_demo.html なので、通常実行の出力を上書きしません
-o, --output OUTPUT出力 HTML ファイル(デフォルト: erd.html
--no-htmlHTML 図を一切生成せず、指定した他の出力だけを書き出します — --emit-* ファイルや --excel ワークブックの生成が目的の CI 実行で便利です。--emit-json/--emit-config/--emit-digest/--emit-dbml/--emit-mermaid/--emit-plantuml/--excel のいずれか1つ以上が必須で、指定がないと何も出力されないためエラー終了します。-o/--output とは併用不可
--models PATHアプリケーションコードから解析した関連セマンティクスをマージ: Rails プロジェクト(または app/models ディレクトリ)、schema.prisma、Django プロジェクト、SQLAlchemy のモデルディレクトリ、Laravel の app/Models ディレクトリ、または Rails の db/schema.rb ファイル — ソースは自動判別されます。繰り返し指定して複数のフレームワークをマージできます(競合時は後に指定した方が優先)。曖昧さのない、型を明示した代替手段については設定のsources:を参照してください
--adapter PATH独自のデータベースアダプタ(DBAdapter)やフレームワークオーバーレイ(FrameworkOverlay)を登録する Python プラグインを読み込みます — 拡張を参照。繰り返し指定可。config の adapters でも指定できます
--excel FILE.xlsxテーブル定義書も出力: 概要シート+テーブルごとのシート
--excel-template FILE.xlsxワークブックの色/フォント/罫線をテンプレート .xlsx から上書き — 5セル契約についてはエクスポートを参照。--excel なしでは効果がなく(警告が出ます)
--emit-json FILE.jsonHTML と並べて、正規化された JSON スキーマスナップショット(provenance と内容フィンガープリント付き)も書き出します — エクスポート参照。- で標準出力へ。HTML も生成されます(--no-html 指定時を除く)
--emit-config FILE.yml|.yaml|.json最終スキーマを、--configで再取込可能な設定ファイル形式でも書き出します — エクスポート参照。拡張子で形式を判定(-で標準出力、常にJSON)。HTML も生成されます(--no-html 指定時を除く)
--diff SNAPSHOT.json出力を生成する代わりに、保存済みの --emit-json スナップショットと比較します — エクスポート参照。CLI 専用(config キーなし)。--emit-json/--emit-config/--emit-digest/--emit-dbml/--emit-mermaid/--emit-plantuml/--excel とは併用不可
--diff-provenance--diff で、association の provenance/sources も比較する(デフォルトでは無視)
--diff-exit-zero--diff で、差分があっても終了コード 0 にする
--diff-format text|json--diff の出力形式を人間可読テキスト(デフォルト)または決定論的な JSON にする
--emit-digest FILE.mdHTML と並べて、設計メモ付きの、LLM/エージェント向けトークン効率の良い Markdown ダイジェストも書き出します — エクスポート参照。- で標準出力へ。HTML も生成されます(--no-html 指定時を除く)
--digest-verbose--emit-digest で、各カラムに nullable/default/sql_type も含める(デフォルトでは省略)
--emit-dbml FILE.dbmlスキーマの最小限の DBML エクスポート(テーブル/カラム/インデックス/単一カラム FK のリレーション/テーブルコメント)も書き出します — エクスポート参照。- で標準出力へ。notes/groups/TableGroup(後回し)は含みません。HTML も生成されます(--no-html 指定時を除く)
--emit-mermaid FILE.mmdスキーマの Mermaid erDiagram エクスポート(テーブル/カラム/PK・FKマーカー/リレーション)も書き出します — エクスポート参照。- で標準出力へ。notes/groups は含みません。HTML も生成されます(--no-html 指定時を除く)
--emit-plantuml FILE.pumlスキーマの PlantUML エンティティ関連図エクスポート(テーブル/カラム/PK・FKマーカー/リレーション)も書き出します — エクスポート参照。- で標準出力へ。notes/groups は含みません。HTML も生成されます(--no-html 指定時を除く)
--max-rows Nテーブルごとにスクロールなしで表示するカラム数の上限(デフォルト: 15)
--only 'user*,post*'指定した glob パターンに一致するテーブルのみを含める。繰り返し指定可、カンマ区切りリストも可
--exclude '*_logs'指定した glob パターンに一致するテーブルを除外。--only と同じ構文
--infer-fk実際の association/FK による裏付けがない場合に、*_id というカラム名から関連を推測する。デフォルトはオフ — トラブルシューティング参照
--table-map 'Widget=crm_widgets'Rails / Laravel 専用: 静的解析でテーブルを特定できないモデルのテーブルを上書き。繰り返し指定可、カンマ区切りリストも可 — トラブルシューティング参照
--config PATH設定ファイルからデフォルト値を読み込む。指定がない場合はカレントディレクトリの .erdscope.json/.yml/.yaml が自動検出されます
--no-configカレントディレクトリに .erdscope.* があっても自動検出をスキップする
--no-open生成後にブラウザを自動で開くのをスキップする。demo(デフォルトでブラウザを開く)にのみ関係します。それ以外の通常実行では受理はされますが何もしません
--version, -Vバージョン文字列を表示して終了する
-h, --help全オプションのヘルプを表示して終了する

ビューアガイド #

以下はすべて、生成された HTML ファイル自体 — つまりブラウザで開く図そのもの — について説明しています。 CLI については触れません。読みながらライブデモで 実際に各コントロールを試してみてください。

ペイン #

ページは3つのペインで構成されています: Tables(左 — チェックボックス付きの テーブル一覧と検索)、diagram(中央の図)、Details(右 — 選択中の対象のカラム・インデックス・関連)です。

フォーカスと探索 #

テーブル(一覧上でもキャンバス上でも可)をダブルクリックするとそのテーブルに フォーカスします — 図はそのテーブルと関連テーブルだけに絞り込まれます。フォーカス中のテーブルを再度 ダブルクリックするか、Esc を押すか、✕ Back to overview / Exit focus ボタンのいずれかで全体表示に戻ります。

フォーカス中は上部のバーに 🔍 Focused: <テーブル名> (depth <深度>, <方向>) と表示されます。左ペインのチェックボックスは全体表示にのみ影響し、フォーカス中のビューには影響しません (その旨のヒントも表示されます)。Apply to checks は、フォーカスビューに 現在表示されているテーブルをそのまま全体表示側のチェック状態に反映してから、フォーカスを終了します。

フォーカス(および後述の Auto-expand)が何を引き込むかは、次の3つのコントロールで決まります。

コントロール選択肢効果
Depth(深度)1 / 2 / 3 / フォーカス中のテーブルから何ホップ先の関連まで含めるか
Direction(方向)Both / Deps / DependentsDeps はこのテーブルが依存しているもの(FK で参照している親)をたどります。Dependents はこのテーブルに依存しているもの(このテーブルを参照している子)をたどります。Auto-expand の設定に関わらず、各テーブルの ボタンにもこの設定が適用されます。
Auto-expand(自動展開)ツールバーのチェックボックスオンにすると、全体表示でチェックされているすべてのテーブルが起点となり、現在の深度/方向の範囲でその関連テーブルを引き込みます。フォーカスモードでは、この設定に関わらず常にフォーカス中のテーブルから展開します。

Auto-expand をオフにすると、それ以上の展開が止まるだけで、既に引き込まれていた テーブルが取り消されることはありません。それらのテーブルは、通常の破線(現在も自動展開でライブに 表示されている AUTO)よりも細かい破線の KEPT 表示のまま画面に残り、 チェックする(ボタン、またはロックが外れた一覧チェックボックスをチェックする)か、 取り除く(ノードの ボタン)まで残り続けます。Auto-expand を再度オンにすると KEPT テーブルはクリアされ、展開は最初から再計算されるため、オン/オフを繰り返しても表示範囲がじわじわ 広がっていくことはありません。一覧では、ライブに表示中の AUTO 行や保持中の KEPT 行には(すべての自動表示行に付く青い丸だけでなく)文字のタグが付きます。 Auto-expand がオンの間、一覧の緑色の は、チェック済みテーブルのうち実際に展開起点と なっているテーブルを示します — 通常のチェック済みテーブルには何も付きません。チェックボックス自体に 曖昧さがないためです。図上のノードには、これと同じテーブルに従来どおり バッジが付きます。 KEPT テーブルをチェックするとタグは新しいものに変わるのではなく単に消え、チェックが入ったこと自体・ 確認トースト・ノードの点滅が「チェックされた」ことの合図になります。

各ノードには ボタンもあり、そのテーブルの直接の関連テーブルだけを(現在の方向設定に 従って)一時的に引き込みます — それ自体が新たな展開の起点になることはないため、1回のクリックでスキーマ 全体が芋づる式に広がることはありません。

orders テーブルに depth 2・Deps 方向でフォーカスした状態。orders とその上流の依存先(shipments、users、addresses)が表示されている

orders にフォーカス — depth 2、Deps 方向。フォーカスバーに両方の設定が表示されています。ツールバーの depth/direction コントロールは、フォーカス中か Auto-expand がオンのときだけ表示されます。

テーブルの非表示 — 2段階 #

erdscope にはテーブルを表示から外す独立した2つの方法があり、強さが異なります。

Exclude(除外)Ban(禁止/完全非表示)
トリガー一覧のチェックを外す、またはノードの ボタン一覧の行にある 🚫 ボタンのみ
強さ軽い — Auto-expand によって他のテーブルの関連先として再び現れたり、チェックを入れ直したり、他のテーブルの から戻ってくることがあります強い — 禁止解除するまで、Auto-expand であっても二度と表示されません
元に戻すチェックを入れ直すその行で再度 🚫、または何か禁止されている間だけ表示される赤いバナー内の Unban all

KEPT テーブルのチェックボックスは(元々チェック済みだったことがないため)既に 外れた状態なので、そこにチェックを入れ直すと除外ではなくチェックになります(通常の チェック済みテーブルにはタグが付かないため、KEPT タグは単に消えます)— KEPT テーブルを 実際に表示から外すにはノードの ボタンを使ってください。

フォーカス中のテーブルを禁止するとフォーカスは終了します。除外セットと禁止セットはどちらもブラウザに 記憶され、名前付きビューや共有リンクにも含まれます。

左ペインに赤い「banned: 1 table(s)」バナーと Unban all リンクが表示され、その下に products の行が取り消し線付きでグレーアウトしている

一覧の 🚫 ボタンで products を禁止した状態 — 取り消し線が引かれ、チェックボックスはロックされ、上の赤いバナーにカウントされます。

erdscope には性質の異なる2つの検索ボックスがあります。

フィルタ(左ペイン、"Search tables / columns…")

テーブル名・カラム名・カラムのコメント・テーブルのコメント、およびテーブル・関連・図全体に付いたメモに一致します。入力するたびにテーブル一覧をその場でフィルタします。Enter を押すと図の中の最有力候補にジャンプし(表示から外れていれば戻し)、一致したカラムをハイライトします。

ハイライト(ツールバー、"Highlight…")

一致対象はおおむねフィルタと同じですが、表示からは何も取り除きません — 一致したテーブル/カラムに印を付け、それ以外を薄暗くするだけの、あくまでオーバーレイです。table/relation メモにも一致しますが、図のノードにしか印を付けられない性質上、テーブルを持たない global メモには一致しません — そちらはフィルタ側で見つけてください。Enter/Shift+Enter で次/前の一致に移動します。選択状態と違い、ハイライトの検索語は PNG/SVG エクスポートにも引き継がれます。

どちらのボックスも、独立した Aa(大文字小文字を区別)と .*(正規表現)の 切り替えボタンを持っています — フィルタ側で正規表現モードを有効にしても、ハイライト側には影響しません。

ツールバーのハイライトボックスに 'user' と入力し、一致したテーブルとカラムがアンバー色の枠で示され、無関係なテーブルは薄暗く表示されている

user をハイライト — 一致したテーブル/カラムにアンバー色の枠が付き、それ以外は薄暗くなりますが、図から取り除かれるものは何もありません。

レイアウトと選択 #

payments・shipments・order_coupons の3テーブルが青くハイライトされて複数選択され、右ペインの Align と Distribute のボタングループが両方とも有効になっている

3テーブルを選択した状態 — 3つ以上選択すると、Align(整列)とDistribute(分布)の両方が使えるようになります(2つの選択では Align のみ有効)。

カラム表示モード #

ツールバーのコントロールで、すべてのテーブルの表示を All(全カラム)、 PK/FK(主キー・外部キーのみ)、Name(テーブル名のみ、カラムなし)の いずれかに切り替えられます。各ノードにも独自の ボタンがあり、そのテーブルだけの 表示モードを個別に切り替えられます — 1つの大きなテーブルだけを詳しく見つつ、残りはコンパクトに保ちたい ときに便利です。全体のモードを切り替えると、テーブルごとの個別設定はリセットされます。

全テーブルが PK/FK モードで主キー・外部キーのみを表示する中、categories だけが個別に Name モードへ切り替えられている図。下部ツールバーでは PK/FK が選択されている

全体は PK/FK モード(ツールバーで選択中)、categories だけ ボタンで Name モードに個別上書きした状態。各ノードの「+N cols」は畳まれたカラム数を示します。

論理名 #

テーブルの DB コメントは、検索可能な「論理名」を兼ねます — 例えば users(Customer accounts) のように表示されます。ツールバーのトグルで、ライブビューの表示を Both / Physical / Logical のいずれかに切り替えられます。エクスポートにはエクスポートパネル内に独自の、 独立した Both/Phys./Log. の選択肢があります(エクスポート参照)— 今見ている表示を 変えても、エクスポートされる内容は変わりません(その逆も同様です)。

論理名(Logical)表示の図。各テーブルが Customer orders や Sellable products などの論理名で表示され、コメントを持たない shipments だけが物理名のまま表示されている

Logical 表示 — 各テーブルは論理名(DB コメント)で表示されます。コメントのない shipments は物理名のままになる、という自然なフォールバックも見て取れます。

名前付きビューと共有 #

💾 Save は名前の入力を求め、現在のビューをその名前で保存します (既存の名前を指定すると上書きされます)。保存されるビューには、除外テーブル、禁止テーブル、 Auto-expand をオフにした後も画面に残っている(KEPT)テーブル、Auto-expand、depth、direction、 カラム表示モード、そして現在表示中の各テーブルの位置が含まれます。

保存済みビューに含まれないもの 名前表示モード(Both/Physical/Logical)、ダークモード、max-rows、ラベルの表示/非表示は、保存済みビューや 共有リンクには含まれません — これらはページ全体に対する別個の設定です。

Views… ドロップダウンから保存済みビューを読み込むか、🗑 で削除できます。🔗共有リンクをコピーします — 現在のビューを JSON エンコードしてページ自身の URL に付加したものです。そのリンクを開くと自動的に 同じビューが再現されるので、PR の説明文やチャットにそのまま貼り付けられます。

ビューを Order fulfillment という名前で保存した直後のトップバー。Views セレクタに Order fulfillment と表示され、隣に Save・削除・共有リンクのボタンが並ぶ

「Order fulfillment」として保存した直後のトップバー — セレクタに適用中のビュー名が表示され、隣に 💾 Save🗑(削除)・🔗(共有リンク)が並びます。

エッジの種類 #

経路: 関連線は、パスが空いている限りテーブル枠間の直線を優先します — 近接テーブル、斜め配置、複数 association を1本にまとめたペアでも同様です。表示中の別テーブルが直線を遮る場合のみ、1〜2回の直角折れ(直交デトゥール)で非端点テーブルの周囲に小さなクリアランスを取って迂回します。自己関連は従来どおり小さなループです。関連の本数を曲線で表現することはありません。グループ枠は見た目のコンテナであり、経路障害にはしません。

図には3種類の関連が反映されますが、キャンバス上の線のスタイルは2種類しかなく、 残りは Details ペインのバッジで区別されます。

manual バッジは、設定ファイルの relations から来た association を示します。(破線のエッジとは別に)破線のノードの枠は、そのテーブルが 直接チェックされたのではなく Auto-expand によって引き込まれたことを示します — Auto-expand が現在も ライブに展開中なら通常の破線(AUTO)、Auto-expand によって引き込まれた後にオフにされた ものなら、より細かい破線(KEPT)になります(フォーカスと探索 を参照)。

payments の詳細パネルの Associations 一覧。belongs_to :order に DB FK バッジ、belongs_to :shipment に manual バッジ、belongs_to :coupon に inferred バッジが付いている

payments の Associations — 実 FK 制約(DB FK)、設定ファイル由来(manual)、名前ベース推測(inferred)の3種類のバッジが1つの一覧に並んだ状態。

データディクショナリ(Schema Grid) #

図はリレーションを辿るのに向いていますが、全カラムを一度に見渡したいときや、スキーマを スプレッドシートで読みたい相手に渡すときは、ただのスクロール可能な表のほうが速いことがよくあります。 左ペインの 📋 Grid ボタンはまさにそれを開きます — スキーマ全体を 覆うフルスクリーンのデータディクショナリモーダルで、背後の図が現在フォーカス中か、 非表示にしているか、除外しているかとは無関係に動作します。

2つのタブが1行が何を表すかを切り替え、それぞれライブのカウントを持ちます: Tables (n)Columns (n)。別のトグルが各行の表示量を制御します: Compact はキーとメモを小さなバッジに詰め込み、Detailed(デフォルト)は それらを完全なテキストへ展開します — 主キー/外部キーの参照先、インデックス定義、メモ本文をその場で 表示します。

スコープセレクタはフィルタボックスの対象を All FieldsTable NameColumn Name のいずれかに絞り込み、加えて Exact match トグルがあります。 Table Name がデフォルトのスコープです — 0.12.1 で All Fields から変更されました。理由は、All Fields が各テーブルの外部キー詳細文字列 (例: user_id -> users.id)も検索対象にしていたためです。そのため、ハブとなる テーブル名(例: users)を入力すると、そのテーブルと、そこへ外部キーを持つすべての テーブルが一致してしまい、小規模なスキーマでは容易に「すべてのテーブル」になり、フィルタを かけていないのと見分けがつきませんでした。All Fields はスコープセレクタからワンクリックで 選べるままなので、テーブルやカラムそのものだけでなく名前に関連するものすべてを意図的に見たいときは、 こちらが正しい選択です。

コントロール下の各エクスポートボタンは現在表示中のビュー — 今どのタブ・どの フィルタが表示されているか — に対して動作します: 📋 TSV / ⬇ TSV📋 CSV / ⬇ CSV はそれをコピーまたはダウンロードします。Excel にきれいに貼り付け られるのは TSV です。Open read-only view ↗ は同じデータディクショナリ — 今この瞬間のスキーマのスナップショットで、下記で追加した内容も含みます — を、独立した読み取り専用の ページとして新しいタブで開きます。スキーマを編集させたくない相手に渡すのに向いています。

Data Dictionary モーダルが Tables タブ・Detailed モードで開き、Table Name で 'order' に絞り込まれた状態。一致した3テーブル(order_coupons・order_items・orders)の Primary Keys・Foreign Keys・Indexes が完全なテキストに展開され、orders 行のテーブルメモにインラインの Edit/Advanced edit アイコンが添えられている

データディクショナリの Tables タブ、Detailed モードで Table Name により order に絞り込んだ状態 — 0.12.1 以降のデフォルトスコープ。

ブラウザでのメモ編集 #

上記の設計メモ(Notes)は設定ファイルでのメモの書き方を説明しています。 ちょっとした修正のためだけに設定ファイルを経由する必要はありません — メモは実行中のビューア内で直接 編集できます。編集の入り口は3つあります: 右ペインのテーブルの Notes セクションにある + Add Note ボタン、データディクショナリのグリッドでまだメモのない テーブル/カラム行にある + Note ボタン、そして既存のメモがどこに表示されていても (右ペイン、Associations 一覧、global メモ用の凡例、グリッド)その隣に付く ✏️ Edit / 🗑 Delete の組です。

グリッドは同じメモに対して2段階の編集深度を用意しています。鉛筆(✏️)はセル内に 小さなインラインのテキストエリアを開きます — Save / Cancel に加えて Modal... という エスケープハッチもあります。ちょっとした文言修正に向いており、テキストを空にして保存するとメモは 削除されます。歯車(⚙️)は、右ペインの + Add Note ボタンと同じフル機能の メモエディタを開きます: Scope ドロップダウン(Table / Relation / Global)が対応する対象フィールドに切り替わり、加えて TitleNote Content があります。Scope を Global に 切り替えるのが、現時点でブラウザから図全体のメモを作成する唯一の方法です — 専用の 「グローバルメモを追加」ボタンはありません。

リンクはこのエディタを往復できません ブラウザのメモエディタには、メモの links 用の入力欄がありません。設定ファイルで 付けたリンクをここで編集しても消えはしませんが、設定ファイルを直接編集して erdscope を再実行しない 限り、リンクを追加・変更することはできません。

提案スキーマ(ToBe) #

ビューアの他の部分はすべて「実際にそこにあるもの」を扱います — 図もグリッドもメモも、マージ済みの AsIs スキーマから読まれます。データディクショナリの + Proposed Table+ Proposed Column ボタンだけは、まだ 存在しないもの — 誰もマイグレーションや設定ファイルに触れる前に、ブラウザ上で直接下書きする ToBe の変更 — をスケッチできる唯一の場所です。

+ Proposed Table は物理名(必須)、任意で論理名・コメント・ グループを尋ねます。追加されたテーブルには提案扱いの id 主キーカラムが1つ付き、他の テーブルと同じように描画されます。+ Proposed Column は対象 テーブルとカラム名(必須)、任意で論理名・データ型(デフォルト varchar(255))・ NULL 許可・コメントを尋ねます。どちらも即座に反映されます: 新しいテーブルやカラムはすぐに図・ テーブル一覧・グリッドに現れます — 図のキャンバス自体は提案テーブルを他のテーブルとまったく同じ ように描画します。今やそれは、見ている実行中のスキーマの単なる1エントリだからです。まだ実在しない ことを示すのはグリッドだけで、名前の隣に PROPOSED バッジが付きます。

Data Dictionary の Columns タブ。orders テーブルに新しく追加された status_code カラムのカラム名の隣に、黄色い PROPOSED バッジが表示されている

+ Proposed Column で追加した提案カラム — グリッドの PROPOSED バッジだけが、このスキーマの中でまだ実在しないことを示す唯一の印です。

提案エントリは、実装済みになったことを知らない エクスポートされる設定には「提案かどうか」を示すマーカーが一切なく、しかも設定は最優先レイヤーとして マージされます。そのため、実際にそのカラムをデータベースに追加したあとも、この設定エントリは データベースが今報告している内容を上書きし続けます — 反映されたら提案エントリを自分で 設定から削除してください。さもないと、古い ToBe カラムが実物を静かにマスクし続けます。 Config JSON のエクスポートはこれをさらに悪化させます: 提案カラムを1つでも含むテーブルは、その テーブルの全カラム一覧を書き出します(新しいカラムだけではありません)。型が不明なカラムには varchar(255) が補われます — そのため、実データベース側でそれらのカラムを後から変更しても、 設定を刈り込むまで同じように隠され続けます。詳細は下記のConfig JSON と Updated HTMLを参照してください。

キーボードショートカット #

これが全リストです — ズーム、保存、エクスポートにショートカットはありません。ツールバーの ? ボタンから、このショートカット一覧の簡易版と、マウス操作の説明、 そしてこのマニュアルへのリンクをビューア内で直接確認できます。

キー動作
Esc状況に応じて、次の優先順で実行されます: 開いているツールバーのメニュー(Export または ? help)を閉じる → フィルタボックスにフォーカスがあり空でなければクリア → ハイライトボックスも同様にクリア → フォーカスモードを終了 → それ以外の場合はすべての選択を解除
Ctrl/Cmd+Z直前のレイアウト変更を取り消す
Ctrl/Cmd+Shift+Z または Ctrl/Cmd+Yやり直す
Enter(フィルタボックス内)最有力候補のテーブル/カラムにジャンプ
Enter / Shift+Enter(ハイライトボックス内)次/前の一致に移動

ダークモード #

🌙 ツールバーボタンで図のダークモードを切り替えます。これは手動での切り替えのみです — ビューアは OS/ブラウザのカラースキームを自動的には追従しません — 選択内容は次回開いたときのために ブラウザに記憶されます。エクスポート(PNG/SVG)は、現在どちらのモードで見ていても常にライトパレットで 描画されます。

ビューアのダークモード全景。3ペインすべてが暗色になり、右ペインの orders 詳細ではバッジや設計メモも暗色配色で表示されている

🌙 トグルによるダークモード — 図と両ペイン、バッジ、設計メモまで配色が切り替わります(このマニュアルのテーマとは独立した、ビューア自身の設定です)。

印刷 #

専用の印刷ボタンはありません — ブラウザ自体の印刷コマンド(Ctrl/Cmd+P)を使ってください。 印刷用のスタイルシートがすべてのクロム(ペイン、ツールバー、凡例、フォーカスバー)を自動的に隠し、 プレーンな白背景に図だけを残します。

エクスポート #

ツールバーの ⬇ Export ボタンを押すと、上部に画像オプション、下部に フォーマットごとの行が並んだパネルが開きます。各行には CopyDownload の別々のボタンがあります — 画像をクリップボードに書き込めるブラウザであっても、 ファイル保存という選択肢を奪わないよう、コピーとダウンロードは意図的に別のアクションとして分離されています。

エクスポートパネルが開いた状態。Join-table labels がチェック済み、root badges が未チェック、Names: Both の画像オプションの下に、PNG・SVG・Mermaid・PlantUML・DBML の5つのフォーマット行が Copy と Download ボタンとともに並んでいる

エクスポートパネル: 上部に画像オプション、下部にフォーマットごとの Copy/Download 行。

画像オプション PNG / SVG のみ

これら2つのチェックボックスと1つのトグルは、PNG と SVG のエクスポートにのみ適用されます — Mermaid、PlantUML、DBML、Excel ワークブックには影響しません。

ライブのハイライト検索は、PNG と SVG のどちらのエクスポートにも引き継がれます (常に取り除かれるテーブル/エッジの選択状態とは異なります)— ハイライトされた図をそのまま ドキュメントに貼り付けられるようにするためです。

PNG

図をまず SVG として描画し、オフスクリーンの <canvas> に読み込んでから、 最大2倍のスケールでラスタライズします — canvas のサイズが約8000pxを超えそうな場合は (それより下に)クランプされます。ブラウザは canvas サイズの上限をそれよりずっと低く設定していることが多く、 超えると toBlob() がサイレントに失敗するためです。Copy は直接クリップボードに 書き込み、ブラウザが画像をクリップボードに書き込めない場合は自動的にファイルダウンロードにフォールバック します。Download は常にファイルを保存します。

SVG

Copy は生の SVG マークアップをテキストとしてコピーします(失敗した場合はダウンロードに フォールバック)。Download.svg ファイルを保存します。エクスポートされる SVG は、ライブビューの現在のダーク/ライト設定に関わらず、常にライトカラーパレットを使用します。

Mermaid

現在表示されているものをすべてカバーする erDiagram ブロックを生成します — フォーカス、非表示、除外の各状態がすべて反映されるため、必ずしもスキーマ全体になるとは限りません。 各テーブルはカラム名・型・PK/FK マーカーを列挙するエンティティブロックになり、各エッジは Mermaid の 鳥の足記法(crow's-foot)を使った関連の行になり、最初の association の名前がラベルとして付きます。 カラムのコメント、null 許可、デフォルト値、論理名は Mermaid の出力には含まれません — 含まれるのは 名前・型・キーマーカーのみです。

PlantUML

Mermaid と同じ範囲・制限(現在表示中のテーブルのみ、カラムコメントなし)ですが、エンティティの マークアップは異なります: 主キーのカラムが区切り線の上に先頭でリストされ、null 非許可のカラムには * が付き、外部キーのカラムには <<FK>> が付き、テーブルに論理名が あれば物理名の横に全角括弧で表示されます。

DBML

Mermaid・PlantUML と同じ範囲(現在表示されているものだけ)を、 dbdiagram.io にそのまま貼り付けられる DBML として書き出します。 各テーブルは Table ブロックになり、カラムごとに1行(型と、該当する場合は pk/ increment/not null/default の設定)が並びます。インデックスまたは 複合主キーがあるテーブルには indexes ブロックが、テーブルコメント(なければ論理名)には Note が付きます。単一カラムかつ非ポリモーフィックな belongs_to のうち、 対象テーブルも表示されているものが Ref: 行になります。CLI の --emit-dbml と同じ内容で、あちらは表示中のサブセットではなく スキーマ全体を対象とします。

Excel ワークブック

--excel FILE.xlsx は、外部のスプレッドシートライブラリを一切使わずにワークブックを 書き出します。

この2つの新しいシートと概要シートの Group 列は、notes/groups がどちらも無い場合は完全に省略され (空のまま残ることはありません)、その結果この機能が入る前とバイト同一のワークブックになります。

--excel-template FILE.xlsx を使うと、erdscope 自体のコードに触れることなく、ワークブックの 色・フォント・罫線を5セル契約を通じて再スタイルできます: テンプレートの最初のワークシート の A 列、1〜5行目に、それぞれ Title / Header / Data / Data (alternate row) / Section のスタイルを 設定しておく必要があります。読み取られるのは各セルのスタイル(フォント、塗りつぶし、罫線)だけで、 セルのテキスト内容は関係ありません。契約セルが欠けている場合は、その1つのロールだけ erdscope 組み込みの スタイルにフォールバックし、stderr に警告が出力されます。.xlsx としてまったく開けない ファイルだけがハードエラーになります。

このリポジトリには excel-template.xlsxgen_excel_template.py によって生成)が、すぐに編集できる出発点として同梱されています — そのStylesシートには、5つの契約セルが erdscope 標準のスタイルであらかじめ設定されており、 B 列には各ロールの平易な説明があります。

注意 --excel-template--excel なしでは効果がありません — スタイル対象となる --excel を指定せずにテンプレートを渡した場合、erdscope は stderr に警告を出力し、それ以外は 無視します。

JSON スナップショット

--emit-json FILE.json- で標準出力)は、最終スキーマを機械可読な正規形に 投影した JSON を HTML と並べて書き出します — HTML はどちらの場合も生成されます。ドキュメントの形は {"format": 1, "fingerprint": "sha256:…", "schema": {"tables": {…}, "notes"?: […], "groups"?: […]}} で、各テーブルは comment(空なら省略)、columnsindexesassociations だけに絞り込まれます(内部用・プラグイン用のキーは一切含みません)。columns/ indexes は決定的な順序・並び替えを経ており、各 association の由来は5つの値(declaredmanualdb_fkschema_fkinferred)のいずれかに 正規化され、分かる場合はその association に寄与したレイヤー集合も付きます。(provenance を持つのは association だけです — テーブル・カラム・index は「どの入力が勝ったか」を記録しません。provenance 付き IR は association 限定です。)参照先が --only/--exclude で残らなかった非ポリモーフィックな association はダングリング のまま出力せず刈り取られます(ポリモーフィックな belongs_to は単一の参照先テーブルを持たない ため常に保持され、その target は実テーブルに対応しない象徴的な名前になります)。 fingerprint はスキーマ内容の sha256 ハッシュで、テーブル/notes/ groups/sources の並び順を変えても変化しません — 同じ入力からは常にバイト同一の出力が得られるため、2つの スナップショットを diff したり、フィンガープリントの変化を CI のゲートにしたりできます。この形とフィンガー プリントは安定した format 1 契約であり、トップレベルの format フィールドで バージョン管理されます — 投影に破壊的変更が入るときは、既存の利用者が読む内容を黙って変えるのではなく format を 2, … と上げます。

設定ファイルエクスポート

--emit-config FILE は、最終スキーマを設定オーサリング形式 — そのまま --config で読み戻せる形式 — で HTML と並べて書き出します。HTML はどちらの場合も生成 されます。拡張子で形式が決まります: .yml/.yaml は YAML(PyYAML が インストールされている必要があります — JSON への無言のフォールバックはせず、明示的にエラーになります)、 .json は JSON、- は標準出力(読み取る拡張子がないため常に JSON)です。 ドキュメントは通常の tables:/notes:/groups: 設定 (設定ファイル)そのものなので、特別な扱いなしに再取込できます。

このファイルを再取込すると、このプロジェクトが level1 と呼ぶ水準に到達します: テーブル、カラム、型/nullable/デフォルト値、主キーのカラム集合、 インデックス(カラム組 + unique の集合)、association(type/target/foreign_key/through/ polymorphic のタプル)、コメント、notes、groups が実質的に同一になる、という意味です — バイト同一の 往復ではありません。provenance、レイヤーごとの sources 集合、config 専用の操作 (drop/*_mode)は、統合スキーマを一度通過すると失われるため、再取込された association は常に manual として表示されます。複合主キーは、スキーマ自身の primary_key フィールドからではなく、どのカラムが個別に primary フラグを持っているかから 再導出されます — DB由来の複合キーはそのフィールドに最初の1カラムしか記録しないため、そこから直接読むと 再取込時にキーが黙って切り詰められてしまいます。この往復を可能にするため、config のいくつかのルールを 緩和しています: インデックスの追加にはもう名前が不要になりました(対象を特定する必要があるdrop だけは引き続き名前が必要です)。relation メモの絞り込みフィールドは、明示的な null (一致対象にその値が存在しないこと)と、キー省略(何にでも一致するワイルドカード)を区別するように なりました。polymorphic な association の target は実テーブルを指す必要がなくなり、それについての メモは、source テーブルさえ生き残っていれば --only/--exclude による絞り込みを 生き延びます。DB裏付けのない Rails 専用テーブルは、その外部キーの背後に実カラムを持つことを要求されなく なりました。YAML の出力は決定論的です — キーはソートされ、複数行のメモ本文はブロックスカラ形式になります — そして、YAML 自身の型推論規則が次回読み込み時にブール値・8進数・その他の数値と誤読しかねないプレーンな 値(たとえばコメントの内容がリテラルに no という文字列である場合など)は、常にクォートされます。 これにより、文字列は常に同じ文字列として戻ってきます。level1 がどちらの経路でも保てないものが1つあります: 空文字のカラムデフォルト(DEFAULT '')と「デフォルト無し」は、どのプロバイダでも読み取り時に 区別できないため、どちらも往復で残りません — --diff が検出できないのと同じ限界です。

ビューアからの変更保存:Config JSON と Updated HTML

ブラウザでのメモ編集提案スキーマ (ToBe)で行った変更はすべて、作業しているそばからブラウザの LocalStorage に自動保存され、 同じ生成済み HTML ファイルを次に開いたときに復元されます — ただし LocalStorage はブラウザの外に 出ることはありません。コミットしたり、添付したり、CLI に戻したりできるファイルにするには、 ツールバーの ⬇ Export メニューのSave proposed changesセクションを使うか、Export ボタンの隣に常時表示されているステータス表示を クリックしてください — 保留中の変更がなければ緑の ✓ Changes exported、メモを 編集するか提案テーブル/カラムを追加した直後は琥珀色の ⚠️ Unexported changesに なります。

どちらの状態でもこの表示をクリックすると、4つのアクションを持つ小さなモーダルが開きます: ⬇ Download Config JSON📄 Download Updated HTML📋 Copy Config JSON、そして Discard & Reset Changes(ページを再読み込みし、未エクスポートの 変更とすでに永続化された変更の両方を消去します)。同じ2つの形式は、このモーダルを介さずに Export メニューの Copy/Download ペアからも直接利用できます。エクスポートすると、表示は ✓ Changes exported に戻ります。

2つの形式がある理由: Config JSON は通常の設定 ファイルです — メモ、グループ、ブラウザで追加した提案テーブル/カラムを含みます — CLI に --config(次回実行時に変更を再適用するため)または --diff(CI で 差分をゲートするため)で戻すことを想定しています。これは--emit-config が生成するのと同じ形ですが、スキーマ全体 ではなくブラウザで触れたテーブルだけに絞られています。Updated HTML はその逆です — 変更がすべて 埋め込み済みの、この同じビューアの新しい自己完結コピーで、CLI も設定ファイルも再実行も不要で、 ファイルを開いて見るだけでよい相手に渡すためのものです。

提案エントリは実装後も消えず、エクスポートも自分自身だけに絞られない 設定には「これは提案だった」というマーカーがなく、設定は最優先レイヤーとしてマージされるため、 実際にそのカラムをデータベースに追加したあとも、このファイルはデータベースが今報告している内容を 手動で削除するまで上書きし続けます。さらに、提案カラムを1つでも含むテーブルの Config JSON エクスポートは、そのテーブルの全カラム一覧を書き出します(新しいカラムだけではありません) — 型が不明なカラムには varchar(255) が補われます。そのため、実データベース側の 後からの変更も、設定を刈り込むまで同じようにマスクされます。上記の提案スキーマ(ToBe)を参照してください。

スキーマ diff / drift ゲート

--diff SNAPSHOT.json は、このrunを以前保存した --emit-json スナップショットと比較し、出力を生成する代わりに差分を報告します — --diff の実行では HTML、Excel、JSON スナップショット、config ファイル、digest、DBML、Mermaid、PlantUML のいずれも書き出されず、 --emit-json/--emit-config/--emit-digest/--emit-dbml/ --emit-mermaid/--emit-plantuml/--excel とは併用できません (使用エラー、終了コード 2)。比較は level1設定ファイル エクスポートと同じ「実質的に同一のスキーマ」という考え方 — で行われ、バイト同一ではありません。 added は「この run にのみ存在する」、removed は「スナップショットにのみ 存在する」を意味し、それがすべての階層(テーブル、そして各共通テーブル内のカラム/インデックス/ association、さらに notes と groups)で一貫しています。インデックスは (columns, unique) で照合されるため単純な改名は検出されません。association は (type, target, name, foreign_key, through, polymorphic) で照合され、 --diff-provenance を渡さない限り provenance/sources は無視されるため、外部キーの retarget(またはそのフラグ下での provenance のみの変更)は「変更」1件ではなく、1つの relation の 削除とその代替の追加として表示されます。

終了コードがゲートの判定信号です: 2つが level1 で同一なら 0、差分があれば 1(CI のステップを失敗させずに差分だけ報告したい場合は --diff-exit-zero を渡します)、使用エラーまたはスナップショットが読み込めない/不正な場合(ファイルが存在しない、JSON が 不正、あるいは --emit-config ファイルのようにトップレベルの format/ schema を持たない「本物ではない --emit-json スナップショット」)は 2 です。スナップショット自身の fingerprint がこの run のものと一致 する場合、2つは自明に同一であるとみなされ、完全な比較はスキップされます。--diff-format text (デフォルト)はサマリー件数と +added/-removed/~changed の行を出力し、--diff-format json は同じ構造をスクリプト用に決定論的な JSON として 出力します。--emit-config と同様に、空文字列のカラム default とデフォルト値なしを 区別できるプロバイダは存在しないため、--diff もその変更だけは検出できません — これは既知の level1 の限界であり、特別扱いする価値のあるものではありません。

LLM/エージェント向け Markdown ダイジェスト

--emit-digest FILE.md- で標準出力)は、最終スキーマを HTML と 並べて、トークン効率の良い Markdown として書き出します。生のスキーマや完全な JSON スナップ ショットの代わりに、LLM/エージェントへ貼り付けたり読ませたりすることを想定しています。 JSON スナップショットと同じ正規スキーマを投影するため、同じ決定論的な 順序付けと、宙に浮いた association の刈り込みを引き継ぎますが、何を残すかは異なります — provenance、sources、そしてデフォルトではカラムの nullable/ default/sql_type はすべて省かれ、あらゆる DB レベルの詳細ではなく スキーマの形にトークンコストを充てます(--digest-verbose を渡せばこの3つも 戻せます)。ダイジェストだけが運べて生のスキーマには載らないもの、それが設計メモ です — global/table/relation の各 notes(Notes)はすべてインライン でレンダリングされます: global note は冒頭の段落として、table note はそのテーブルの見出しの下に、 relation note はそれが対象とする association の行に追記されます。groupsGroups)は、正規スキーマのフィールドのうちダイジェストに一切現れない 唯一のものです — これはビューア上のレイアウト補助(どのテーブルを1つの枠で囲んで描くか)であって、 このファイルを読む LLM が推論する必要のあるスキーマの意味ではないためです。各テーブルは見出し (とそのコメント)、カラムごとの箇条書き1行(name: type に加え、該当すれば pk/fk→target/クォートされたコメント)、そして association を要約する 1行の Rel: 行(type target[ as name][ fk=…][ through …][ (poly)])で 構成されます。association を持たないテーブルは Rel: 行を単に省きます。JSON スナップショットと同様、同じスキーマは常にバイト同一の Markdown をレンダリングします。

DBML エクスポート

--emit-dbml FILE.dbml- で標準出力)は、最終スキーマを HTML と 並べて、最小限の DBML として書き出します — テーブル、カラム、主キー、インデックス、単一カラム FK のリレーション (Ref:)、テーブルコメント(Note:)です。typed DBML 入力ソースを補完する 機能であり、DBML を統合スキーマへ読み込んだうえで、その最終結果を以下の再現範囲に従って エクスポートできます。 高精度に再現されるもの: 各カラムは生の sql_type(DB 型が 記録されていない場合のみ、粗い type 表記にフォールバック)、pk/ increment/not null/unique/default の各属性、 複合主キーは indexes { (a, b) [pk] } エントリとして、それ以外のインデックスは (名前の有無・unique の有無を問わず)それぞれの indexes エントリとして、 テーブルのコメントは末尾の Note: 行として(コメント自体が複数行の場合は トリプルクォートで)レンダリングされます。今回は明示的に含まれないもの (後続の拡張フェーズへ先送り): notes/groupsNotes/Groups — DBML 自体の Project/TableGroup ブロックは groups の受け皿として自然ですが、 そのマッピングはまだ実装されていません)、has_one のリレーション、 polymorphic なリレーション、複数カラムの外部キー、そして単一カラムの主キーを持たない ターゲットへのリレーションです。Ref: の生成ルールは意図的に狭くしてあります — Ref: 行を生成するのは foreign_key を持つ belongs_to association だけです。理由は、belongs_toforeign_key は erdscope の どのプロバイダでも必ずその association 自身の(宣言元の)テーブル上のカラムを指すのに対し、 has_oneforeign_key は曖昧だからです — ほとんどのプロバイダは これも宣言元テーブルのカラムとして扱いますが、Rails の手書き has_one :x, foreign_key: :y はその逆で、y は*相手側*テーブルの カラムを意味します。このエクスポートに到達した時点では、その has_one がどの プロバイダ由来かをスキーマだけから判別できません。存在しないカラムを指す Ref: を誤って出力するリスクを取るよりは、has_one/ has_many/has_and_belongs_to_manyRef: 生成から 完全に除外しています。polymorphic な belongs_to は黙ってスキップされます (そのターゲットは実テーブルではなく合成のプレースホルダーだからです)。ターゲットに主キーが ない、または複合主キーである belongs_to は、この最小版では単一カラムの Ref: として表現できないためスキップされ、標準エラー出力に警告が出ます (処理を失敗させることはありません)。他のエクスポートと同様、これも決定論的です — 同じ スキーマは常に同じ DBML テキストをレンダリングします — そして純粋な追加機能です: 既存の HTML/Excel/--emit-json/--emit-config/--emit-digest の出力には影響せず、他の出力系オプションと同様に --diff とは併用できません (使用方法エラー、終了コード 2)。

Mermaid 図エクスポート

--emit-mermaid FILE.mmd- で標準出力)は、最終スキーマを HTML と 並べて MermaiderDiagram として書き出します。これは上記の DBML エクスポートのようなスキーマ定義エクスポートではなく、軽量なエクスポート です — 辺の存在、crow's-foot 記法によるカーディナリティ(一対一/一対多/多対多)、各カラムの 名前・粗い型・PK/FK マーカーのみを含み、インデックスや default、Ref: 相当の精度は ありません。これはビューア自身の Mermaid コピー/ダウンロードボタンが、現在画面に表示されている テーブルのサブセット(非表示/ban されたテーブルは除外、フォーカス/自動展開状態を反映)に対して 既に生成しているものと同じ内容です — その「今見ているものをエクスポートする」インタラクティブな 挙動はそのまま残しつつ、スキーマ全体(または --only/--exclude で 絞り込んだもの)に対して非対話的に、スクリプトや README/PR への貼り付け用途で使えるようにした ものです。カラムの型は粗い type フィールドから(sql_type ではなく) レンダリングされます — Mermaid は忠実度優先のスキーマ形式ではなく図表記法だからです。 through/has_and_belongs_to_many のみで結ばれた関連は多対多として、 has_many/belongs_to のペア(またはどちらか一方)は多側が正しい向きに なる一対多として、has_one は一対一としてレンダリングされます。決定論的(同じ スキーマは常に同じテキストを生成)かつ純粋な追加機能で、既存の HTML/Excel/他のエクスポートには 影響しません。notes/groups は含まれず、他の出力系オプションと同様に --diff とは 併用できません(使用方法エラー、終了コード 2)。

PlantUML 図エクスポート

--emit-plantuml FILE.puml- で標準出力)は、最終スキーマを HTML と 並べて PlantUML のエンティティ関連図として書き出します — 上記の Mermaid エクスポートと同じ範囲・同じ軽量図の位置づけです(辺の存在+カーディナリティ+カラムの 名前・粗い型・PK/FK のみ、インデックス/default/Ref: 相当の精度なし)。ビューア自身の PlantUML コピー/ダウンロードボタンと同じ内容を、スキーマ全体(または --only/ --exclude で絞り込んだもの)に対して非対話的にミラーします。PlantUML の識別子として そのまま使えないテーブル名にはサニタイズ済みのエイリアスが割り当てられ(entity 宣言とそれを 参照する全てのリレーション行の両方で一貫して使われます)、テーブルコメントは表示名の末尾に 全角括弧で付加されます(テーブル名(コメント))— ビューア自身の日本語フレンドリーな 書式に合わせたものです。上記の Mermaid エクスポートと同じ決定論性・純粋な追加機能の保証があり、 notes/groups は含まれず、他の出力系オプションと同様に --diff とは併用できません (使用方法エラー、終了コード 2)。

トラブルシューティング / FAQ #

日本語(またはマルチバイト)のコメントが文字化けする

erdscope は PyMySQL 経路でも mysql CLI フォールバック経路でも、明示的に utf8mb4 で接続します — サーバーやセッションのデフォルト文字セットには頼りません。それでも コメントが化けている場合、最も可能性が高いのは、データベースに書き込まれた時点ですでに壊れていた というケースです(例えば以前に utf8mb4 でない接続経由でインポートされた場合など)— erdscope のバグだと判断する前に、utf8mb4 であることが確実なクライアントで、データベース内の コメントを直接確認してみてください。

PyMySQL / psycopg がインストールされていない場合はどうなりますか?

erdscope は代わりにデータベース自身の CLI にシェルアウトします — MySQL なら mysql、 PostgreSQL なら psql(クエリを COPY … TO STDOUT でラップします。このエスケープ 済みテキスト形式のおかげで、自由記述のコメントが出力を壊すことがありません)。どちらも PATH 上にある必要があります。パスワードはこの場合も MYSQL_PWD / PGPASSWORD 環境変数 経由で渡され、CLI 引数として渡されることはありません。この2つの経路は、バイト単位で同一の HTML を生成 します。ドライバと CLI のどちらも利用できない場合は、どちらかをインストールするよう促す明確な エラーが表示されます — erdscope がここでサイレントに失敗することはありません。

VIEW が図に表示されないのはなぜですか?

これは仕様です — erdscope は実際にストレージに裏付けられたテーブルのみを読み込みます: MySQL では TABLE_TYPE = 'BASE TABLE'、PostgreSQL では通常のテーブルとパーティション親テーブルのみ (VIEW も個々のパーティションも対象外)です。これにより、図は実際にストレージに裏付けられたスキーマに 絞り込まれます。

--infer-fk がデフォルトでオフなのはなぜですか?

単なる *_id というカラム名は推測にすぎず、それを裏付けるもの(実際の association や DB FK 制約)が何もない場合、間違っている可能性があるためです。このフラグをオンにしても、その推測が 遡って昇格するわけではありません — カラム一覧の「FK」バッジと PK/FK カラム表示モードは、あくまで実際の association だけに基づいており、推測によるエッジは常に見た目でも区別されます(薄い点線になります。 エッジの種類参照)。検証済みの関連と同じ見た目になることはありません。

--table-map はどんなときに必要ですか?

これは Rails / Laravel 専用の抜け道で、erdscope の静的解析ではモデルの実際のテーブルを特定できない、 まれなケースのためのものです — 最も多いのは、アプリ自身にはソースがなく、gem/パッケージから提供される concern/トレイトや基底クラスの中に、Rails の self.table_name = ... (または Laravel の protected $table = ...)の代入がある場合です。 --table-map 'Widget=crm_widgets'(繰り返し指定可、カンマ区切りリストも可)を渡すことで 明示的に上書きできます。この上書きは、そのモデルを指す他のモデルの association も修正するため、 右ペインのリンクは正しいテーブルに解決されます。

Auto-expand がオンなのに関連テーブルが表示されません

Auto-expand は、チェック済み/フォーカス中の起点から BFS(幅優先探索)でたどり着けるテーブルだけを 引き込みます。この探索が途中で止まる正当な理由がいくつかあります — 順に確認してみてください。

大規模スキーマ

生成処理そのものは、計測したどのサイズでも高速なままです — SQLite / MySQL / PostgreSQL の パース処理がボトルネックになることはありません。規模が大きくなるにつれて遅くなるのはブラウザ側の ビューアです: 初回描画と、インタラクティブな再レイアウト(「今すぐ再レイアウト」 ボタンや、図を詰め直すその他の操作)のどちらも、テーブル数・エッジ数が増えるほど処理量が増えます。 合成スキーマ(テーブルあたり FK エッジ約2本。手法とスクリプト一式は、リポジトリの benchmarks/ を参照)での計測では、再レイアウトは線形内挿ではおよそテーブル数 970 前後で1秒のラインを超えます。 単一環境での計測値であることを踏まえ、余裕を見てテーブル数 900 前後を実用上の 保守的な上限の目安としてください — 初回描画自体は、少なくとも1,000テーブルまでは3秒を十分に 下回ったままです。

この目安を超える規模では、スキーマ全体を一度に描画するのではなく、絞り込むことをおすすめします:

拡張 #

パース以降のすべて — HTML/JS ビューア、レイアウト、すべてのエクスポート形式 — は、erd.py 自身のモジュール docstring に記載されている、単一の中間表現(IR)を消費します。

tables = {
  "table_name": {
    "primary_key": "id" | ["order_id","item_id"] | None,
                                    # 複合PKはリスト形式。各カラム側の primary=True も立つ
    "comment"?: str,
    "schema_missing"?: bool,        # model exists but no DB table
    "columns": [{"name","type","nullable","primary",
                 "sql_type"?, "default"?, "extra"?, "comment"?}],
    "indexes": [{"name","columns":[...],"unique":bool}],
    "associations": [{"type": has_many|belongs_to|has_one|has_and_belongs_to_many,
                      "name", "target",
                      "through"?, "foreign_key"?, "polymorphic"?,
                      "db_fk"?, "inferred"?, "manual"?, "schema_fk"?}],
  }
}

association の出所フラグ(db_fk / schema_fk / manual / inferred — いずれも無ければ「宣言された association」)は、プラグインが返すそのままの 形です。パイプライン内部ではこれらを provenance/sources 表現に正規化し、出力の直前に同じフラグへ 書き戻すため、プラグイン作者が内部表現を意識する必要はありません。

入力の2つのレイヤーはどちらもプラグイン化されています — データベースアダプタは URL スキームをこの形に変換し、フレームワークオーバーレイ--models のプロジェクトをこの形に変換します。いずれも、担当するスキームやプロジェクト種別の 下に登録される小さなクラスなので、追加してもディスパッチのコードには一切手を入れません。組み込みの db/mysql.pydb/postgres.pydb/sqlite.pyframeworks/{rails,prisma,django,sqlalchemy,laravel}.py が両パターンの実例です。特に parse_postgres() は MySQL アダプタの IR ビルダーをそのまま 再利用し(pg_catalog の結果を同じ5カラムの行の形に整形)、PK の検出・unique インデックスによる 1:1 昇格・インデックスの組み立てをコードを複製せず共有しています。

独自のアダプタ/オーバーレイ #

基底クラスを継承して自分自身を登録する Python ファイルを書き、実行時に --adapter path/to/plugin.py(または config の adapters: [...])で読み込むだけです。 ビルドし直す必要はありません — プラグインは実行中のプロセスに登録され、単一ファイルの erd.py でも pip install erdscope でも同じように動きます:

# my_duckdb.py — 独自のデータベースアダプタ。(MySQL・PostgreSQL・SQLite は
# すでに組み込みなので、これは新しいエンジン(例: DuckDB)の追加例です。)
from erd import DBAdapter, register_adapter, mysql_ir

@register_adapter
class DuckDBAdapter(DBAdapter):
    schemes = ('duckdb',)     # 対応する URL スキーム
    name = 'duckdb'           # 出力に記録される provider ID
    label = 'DuckDB'          # 進捗表示用の表示名

    def fetch(self, url):
        # ...`url` のスキーマを読み取り、IR(`tables` dict)を返す。
        # mysql_ir() が information_schema 形式の行から IR を組み立ててくれます。
        return mysql_ir(table_rows, col_rows, fk_rows, index_rows)
python3 erd.py duckdb:///app.duckdb --adapter my_duckdb.py -o erd.html

フレームワークオーバーレイも --models のパスに対する同じ仕組みです — detect(root)build(root, table_map) を実装し、 @register_overlay で登録します(priority が小さいものから判定され、最初に一致した ものが採用されます)。どちらも通常の Python ファイルなので、1つのプラグインでアダプタとオーバーレイの 両方を登録することもできます。

erd.py のビルド #

配布される erd.py はビルド生成物です。開発用ソースは src/erdscope/ 以下に、 役割ごとの断片ファイルと、2つの自動組み立てフォルダ — db/(アダプタ)と frameworks/(オーバーレイ)— として置かれています。フォルダ内のファイルは自動的に すべて取り込まれ(base.py が先頭、以降はソート順)、組み込みのアダプタ/オーバーレイの追加は フォルダにファイルを1つ置くだけです。ソースを編集してから python3 tools/build_single_file.py を実行すると、依存ゼロの単一ファイル erd.py が再生成されます(ビューアの HTML/CSS/JS は viewer.html から インライン化)。--check は CI と同じく同期状態を検証します。