MongoDBのデータベース/コレクション/インデックスの一覧をCSV/PlantUMLで表示する

## やりたいこと

MongoDBを使った開発を行ってきたが現物とドキュメントとの乖離が出てきたり、他人が設計したものの面倒を見ることになってしまうことが出てきた。なので現物からデータベース/コレクション/インデックスを抜き出し視覚化して概要を把握したい・ドキュメント化したい。

RDBMSでER図を作成する[SchemaSpy](https://schemaspy.org/)みたいなことをしたい感じ。

結果としての出力は

- テキストでツリー形式
- CSV形式(Excelで読んだり…)
- PlantUML
 
が選べる様にしたい。

## 方法

MongoDBは内部でJavaScriptを実行できるので、これでデータベース一覧→コレクション一覧→インデックスの順で順次取得するスクリプトを書いた。

MongoDBでJavaScriptはこんな感じで実行できる。

```sh
$ mongosh mongodb://server:port -f script.js
```

## できたもの

コメント入れて100行以下なので貼っておく。

```javascript:getMongoCollections.js
// 出力フォーマットを得る
const format = process.env.FORMAT || 'text';

// ヘッダー出力
if (format === 'csv') {
  print('Database,Collection,Index');
} else if (format === 'plantuml') {
  print('@startuml');
  print('left to right direction');
  print('scale 300 width');
}

// データベース一覧を取得
const databases = db.adminCommand('listDatabases').databases;

// データベースをそれぞれ調べる
databases.forEach(database => {
  const dbName = database.name;

  // システムデータベースは無視する
  if (dbName === 'admin' || dbName === 'config' || dbName === 'local') {
    return;
  }

  // データベース名を表示
  if (format === 'csv') {
    print(`${dbName},,`);
  } else if (format === 'plantuml') {
    print(`database ${dbName} {`);
  } else {
    print(`Database: ${dbName}`);
  }

  // データベースを切り替え
  const currentDb = db.getSiblingDB(dbName);

  // コレクション一覧を取得
  const collections = currentDb.getCollectionNames();

  // コレクションを調べる
  collections.forEach(collection => {

    // コレクション名を表示
    if (format === 'csv') {
      print(`,${collection},`);
    } else if (format === 'plantuml') {
      print(`  folder ${collection} {`);
    } else {
      print(`  Collection: ${collection}`);
    }

    // インデックス一覧を取得
    const indexList = currentDb.getCollection(collection).getIndexKeys();

    // インデックスのヘッダーを表示(PlantUML)
    if (format === 'plantuml') {
      print(`    card ${collection.replaceAll("-", "_")}_index [`); // PlantUML対策
      print(`      Index`);
    }
    // 各インデックスに対して処理を行う
    indexList.forEach(index => {
      // インデックスを表示
      if (format === 'csv') {
        print(`,,"${JSON.stringify(index).replaceAll('"', '""')}"`);
      } else if (format === 'plantuml') {
        print(`      ....`)
        print(`      ${JSON.stringify(index)}`);
      } else {
        print(`    Index: ${JSON.stringify(index)}`);
      }
    });
    // コレクション終了(PlantUML)
    if (format === 'plantuml') {
      print(`    ]`);
      print(`  }`);
    }
  });
  // データベース終了(PlantUML)
  if (format === 'plantuml') {
    print('}');
  }
});

// フッター出力
if (format === 'plantuml') {
  print('@enduml');
}
```

こちらにも置いておいた。

https://github.com/hkato/getMongoCollections

## 使用方法

`FORMAT`環境変数に`csv`or`plantuml`を入れるとそのフォーマットで表示される。そうでない場合テキストで。

```sh
# テキスト
$ mongosh mongodb://localhost:27017 -f getMongoCollections.js
# CSV
$ FORMAT=csv mongosh mongodb://localhost:27017 -f getMongoCollections.js > output.csv
# PlantUML
$ FORMAT=plantuml mongosh mongodb://localhost:27017 -f getMongoCollections.js > output.puml
```

## サンプル出力結果

オープンソースのかんばんツールWekanはMongoDBを使っているので、それをDocker Composeで起動させその時のMongoDBのコレクション等をサンプルとして見てみる。

```yaml:Compose.yaml
services:
  wekan:
    image: wekanteam/wekan
    ports:
      - "8080:8080"
    depends_on:
      - mongo
    environment:
      - ROOT_URL=http://localhost:8080
      - MONGO_URL=mongodb://mongo:27017/wekan
  mongo:
    image: mongo:latest
    volumes:
      - wekan-data:/data/db
    ports:
      - "27017:27017"

volumes:
  wekan-data:
```

### テキスト

```text
Database: wekan
  Collection: accountSettings
    Index: {"_id":1}
    Index: {"modifiedAt":-1}
  Collection: settings
    Index: {"_id":1}
    Index: {"modifiedAt":-1}
  Collection: invitation_codes
    Index: {"_id":1}
    Index: {"email":1}
    Index: {"modifiedAt":-1}
.
.
.
```

### CSV

Markdownで例示するが、Execl等でも表にするとこんな感じ

|Database|Collection|Index|
|:----|:----|:----|
|wekan| | |
| |accountSettings| |
| | |{"_id":1}|
| | |{"modifiedAt":-1}|
| |settings| |
| | |{"_id":1}|
| | |{"modifiedAt":-1}|
| |invitation_codes| |
| | |{"_id":1}|
| | |{"email":1}|
| | |{"modifiedAt":-1}|

生のCSVだと

```text
Database,Collection,Index
wekan,,
,accountSettings,
,,"{""_id"":1}"
,,"{""modifiedAt"":-1}"
,settings,
,,"{""_id"":1}"
,,"{""modifiedAt"":-1}"
,invitation_codes,
,,"{""_id"":1}"
,,"{""email"":1}"
,,"{""modifiedAt"":-1}"
.
.
.
```

### PlantUML

```plantuml
@startuml
left to right direction
scale 600 width
database wekan {
  folder accountSettings {
    card accountSettings_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder settings {
    card settings_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder invitation_codes {
    card invitation_codes_index [
      Index
      ....
      {"_id":1}
      ....
      {"email":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder customFields {
    card customFields_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
      ....
      {"boardIds":1}
    ]
  }
  folder boards {
    card boards_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
      ....
      {"members.userId":1}
      ....
      {"_id":1,"members.userId":1}
    ]
  }
  folder team {
    card team_index [
      Index
      ....
      {"_id":1}
      ....
      {"teamDisplayName":1}
    ]
  }
  folder __pre_attachments {
    card __pre_attachments_index [
      Index
      ....
      {"_id":1}
      ....
      {"createdAt":1}
    ]
  }
  folder card_comments {
    card card_comments_index [
      Index
      ....
      {"_id":1}
      ....
      {"cardId":1,"createdAt":-1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder announcements {
    card announcements_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder checklistItems {
    card checklistItems_index [
      Index
      ....
      {"_id":1}
      ....
      {"cardId":1}
      ....
      {"modifiedAt":-1}
      ....
      {"checklistId":1}
    ]
  }
  folder integrations {
    card integrations_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
      ....
      {"boardId":1}
    ]
  }
  folder meteor-migrations {
    card meteor_migrations_index [
      Index
      ....
      {"_id":1}
    ]
  }
  folder tableVisibilityModeSettings {
    card tableVisibilityModeSettings_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder attachments {
    card attachments_index [
      Index
      ....
      {"_id":1}
      ....
      {"meta.cardId":1}
    ]
  }
  folder swimlanes {
    card swimlanes_index [
      Index
      ....
      {"_id":1}
      ....
      {"boardId":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder meteor_oauth_pendingCredentials {
    card meteor_oauth_pendingCredentials_index [
      Index
      ....
      {"_id":1}
      ....
      {"credentialSecret":1}
      ....
      {"key":1}
      ....
      {"createdAt":1}
    ]
  }
  folder meteor_accounts_loginServiceConfiguration {
    card meteor_accounts_loginServiceConfiguration_index [
      Index
      ....
      {"_id":1}
      ....
      {"service":1}
    ]
  }
  folder card_comment_reactions {
    card card_comment_reactions_index [
      Index
      ....
      {"_id":1}
      ....
      {"cardCommentId":1}
    ]
  }
  folder org {
    card org_index [
      Index
      ....
      {"_id":1}
      ....
      {"orgDisplayName":1}
    ]
  }
  folder triggers {
    card triggers_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder checklists {
    card checklists_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
      ....
      {"cardId":1,"createdAt":1}
    ]
  }
  folder lists {
    card lists_index [
      Index
      ....
      {"_id":1}
      ....
      {"-modifiedAt":1}
      ....
      {"modifiedAt":1}
      ....
      {"-title":1}
      ....
      {"-sort":1}
      ....
      {"boardId":1}
      ....
      {"archivedAt":-1}
      ....
      {"modifiedAt":-1}
      ....
      {"title":1}
      ....
      {"sort":1}
    ]
  }
  folder actions {
    card actions_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder cards {
    card cards_index [
      Index
      ....
      {"_id":1}
      ....
      {"boardId":1,"createdAt":-1}
      ....
      {"modifiedAt":-1}
      ....
      {"parentId":1}
    ]
  }
  folder cronHistory {
    card cronHistory_index [
      Index
      ....
      {"_id":1}
      ....
      {"intendedAt":1,"name":1}
      ....
      {"startedAt":1}
    ]
  }
  folder users {
    card users_index [
      Index
      ....
      {"_id":1}
      ....
      {"username":1}
      ....
      {"emails.address":1}
      ....
      {"services.resume.loginTokens.hashedToken":1}
      ....
      {"services.resume.loginTokens.token":1}
      ....
      {"services.password.enroll.when":1}
      ....
      {"services.password.reset.when":1}
      ....
      {"services.email.verificationTokens.token":1}
      ....
      {"services.password.enroll.token":1}
      ....
      {"services.password.reset.token":1}
      ....
      {"services.resume.loginTokens.when":1}
      ....
      {"services.resume.haveLoginTokensToDelete":1}
      ....
      {"services.oidc.id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder unsaved-edits {
    card unsaved_edits_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
      ....
      {"userId":1}
    ]
  }
  folder rules {
    card rules_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder __pre_avatars {
    card __pre_avatars_index [
      Index
      ....
      {"_id":1}
      ....
      {"createdAt":1}
    ]
  }
  folder activities {
    card activities_index [
      Index
      ....
      {"_id":1}
      ....
      {"createdAt":-1}
      ....
      {"commentId":1}
      ....
      {"cardId":1,"createdAt":-1}
      ....
      {"boardId":1,"createdAt":-1}
      ....
      {"modifiedAt":-1}
      ....
      {"attachmentId":1}
      ....
      {"customFieldId":1}
    ]
  }
  folder orgUser {
    card orgUser_index [
      Index
      ....
      {"_id":1}
      ....
      {"orgId":-1}
      ....
      {"orgId":-1,"userId":-1}
    ]
  }
  folder translation {
    card translation_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
  folder accessibilitySettings {
    card accessibilitySettings_index [
      Index
      ....
      {"_id":1}
      ....
      {"modifiedAt":-1}
    ]
  }
}
@enduml
```