A5:SQL Mk-2 に頼らない MySQL のテーブル定義出力

こちらは [ZYYX Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zyyx) 2日目の記事です。

テーブル定義書の生成といえば、おなじみの A5:SQL Mk-2 でエクセルベースのテーブル定義書をエクスポートするのが定番ですが、残念ながら Windows のみで Mac に対応していません。
Wine で強引に exe を実行するという方法もありますが、今回はよりカジュアルに Mac でHTML・Markdownでテーブル定義書を書き出す方法をご紹介します。


- `mysqldump` で DDL をXML形式で出力
- `XSLT` をあててHTMLやMarkdown に変換

以上の流れになります。

## DDLのエクスポート

まずは `mysqldump` コマンドでXML形式のDDLをエクスポートします。

```sh
mysqldump -u <ユーザー名> -p --no-data --xml <データベース名> > <出力ファイル名（XML）>
```


## XSLT のアタッチ
dump した XML に `xsltproc` で変換したいフォーマット用のXSLTをあてて変換します。

```sh
# HTMLに変換
xsltproc -o <出力ファイル名（HTML）> htmlstyle.xslt <XMLファイルパス>

# Markdownに変換
xsltproc -o <出力ファイル名（md）> mdstyle.xslt <XMLファイルパス>
```


htmlstyle.xsl（HTMLに変換）

```xml
<?xml version="1.0" encoding="utf8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:s="http://sqlfairy.sourceforge.net/sqlfairy.xml">
	<xsl:output method="html" encoding="utf8" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"/>

	<xsl:template match="database">
		<html lang="ja">
			<head>
				<meta charset="utf-8"/>
				<title><xsl:value-of select="@name"/> テーブル定義</title>
				<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/>
				<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"/>
				<style>
					table.htable {
						margin: 3em auto 1em auto !important;
					}
					table.htable th {
						border-left: 10px solid #e5e5e5 !important;
					}
					footer {
						border-top: 1px solid #e5e5e5;
						padding: 0.5em;
					}
				</style>
			</head>

			<body>
				<div class="container">
					<h1 class="page-header"><xsl:value-of select="@name"/> テーブル定義</h1>
					<xsl:apply-templates select="table_structure"/>
				</div>
				<footer class="text-center">
					your company
				</footer>
			</body>
		</html>
	</xsl:template>

	<xsl:template match="table_structure">
		<h2 class="table-header"><xsl:value-of select="@name"/></h2>
		<table class="table table-bordered htable">
			<tbody>
				<tr class="active">
					<th>論理テーブル名</th>
					<td>
						<xsl:value-of select="options/@Comment"/>
					</td>
				</tr>
				<tr class="active">
					<th>物理テーブル名</th>
					<td><xsl:value-of select="@name"/></td>
				</tr>
			</tbody>
		</table>
		<table class="table table-condensed">
			<thead>
				<tr>
					<th class="text-right">#</th>
					<th>論理名</th>
					<th>物理名</th>
					<th>データ型</th>
					<th>NULL</th>
					<th>デフォルト値</th>
					<th>主キー</th>
					<th>ユニーク</th>
				</tr>
			</thead>
			<tbody>
				<xsl:apply-templates select="field"/>
			</tbody>
		</table>
		<table class="table table-condensed">
			<thead>
				<tr>
					<th>インデックス名</th>
					<th>カラム</th>
					<th>複合キー順序</th>
					<th>NULL</th>
					<th>UNIQ</th>
				</tr>
			</thead>
			<tbody>
				<xsl:apply-templates select="key"/>
			</tbody>
		</table>
	</xsl:template>

	<xsl:template match="field">
		<tr>
			<td class="text-right"><xsl:value-of select="position()"/></td>
			<td><xsl:value-of select="@Comment"/></td>
			<td><xsl:value-of select="@Field"/></td>
			<td><xsl:value-of select="@Type"/></td>
			<td><xsl:if test="@Null='YES'"><span class="glyphicon glyphicon-ok"></span></xsl:if></td>
			<td><xsl:value-of select="@Default"/></td>
			<td><xsl:if test="@Key='PRI'"><span class="glyphicon glyphicon-ok"></span></xsl:if></td>
			<td><xsl:if test="@Key='UNI'"><span class="glyphicon glyphicon-ok"></span></xsl:if></td>
		</tr>
	</xsl:template>

	<xsl:template match="key">
		<tr>
			<td><xsl:value-of select="@Key_name"/></td>
			<td><xsl:value-of select="@Column_name"/></td>
			<td><xsl:value-of select="@Seq_in_index"/></td>
			<td><xsl:choose><xsl:when test="@Null != ''"><xsl:value-of select="@Null"/></xsl:when><xsl:otherwise>NO</xsl:otherwise></xsl:choose></td>
			<td><xsl:choose><xsl:when test="@Non_unique = '0'">YES</xsl:when><xsl:otherwise>NO</xsl:otherwise></xsl:choose></td>
		</tr>
	</xsl:template>
</xsl:stylesheet>
```


mdstyle.xsl（Markdownに変換）

```xml
<?xml version="1.0" encoding="utf8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:s="http://sqlfairy.sourceforge.net/sqlfairy.xml">
<xsl:output method="text" encoding="utf8"/>

<xsl:template match="database">
# <xsl:value-of select="@name"/> テーブル定義
<xsl:apply-templates select="table_structure"/>
</xsl:template>

<xsl:template match="table_structure">

## <xsl:value-of select="@name"/>
物理テーブル名|論理テーブル名
--------|--------
<xsl:value-of select="@name"/>|<xsl:value-of select="options/@Comment"/>

#### カラム情報
#|論理名|物理名|データ型|NULL|デフォルト値|主キー|Extra
----|----|----|----|----|----|----|---|
<xsl:apply-templates select="field"/>

#### インデックス情報
インデックス名|カラム|複合キー順序|NULL|UNIQ
----|----|----|----|----
<xsl:apply-templates select="key"/>
</xsl:template>

<xsl:template match="field">
<xsl:value-of select="position()"/>| <xsl:value-of select="@Comment"/> |<xsl:value-of select="@Field"/>|<xsl:value-of select="@Type"/>|<xsl:value-of select="@Null"/>|<xsl:choose><xsl:when test="@Default != ''"><xsl:value-of select="@Default"/></xsl:when><xsl:otherwise>n/a</xsl:otherwise></xsl:choose>|<xsl:choose><xsl:when test="@Key != ''"><xsl:value-of select="@Key"/></xsl:when><xsl:otherwise>n/a</xsl:otherwise></xsl:choose>|<xsl:choose><xsl:when test="@Extra != ''"><xsl:value-of select="@Extra"/></xsl:when><xsl:otherwise>n/a</xsl:otherwise></xsl:choose>|<xsl:text>
</xsl:text>
</xsl:template>

<xsl:template match="key">
<xsl:value-of select="@Key_name"/>| <xsl:value-of select="@Column_name"/>|<xsl:value-of select="@Seq_in_index"/>|<xsl:choose><xsl:when test="@Null != ''"><xsl:value-of select="@Null"/></xsl:when><xsl:otherwise>NO</xsl:otherwise></xsl:choose>|<xsl:choose><xsl:when test="@Non_unique = '0'">YES</xsl:when><xsl:otherwise>NO</xsl:otherwise></xsl:choose>|<xsl:text>
</xsl:text>
</xsl:template>

</xsl:stylesheet>
```
## 変換文書のイメージ

HTML
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208400/22d32286-0053-3098-1744-a5f338d666fe.png)

Markdown
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208400/4f9096b3-82af-028e-5908-d6eb1d791d53.png)


HTML では bootstrap を読み込んでいるので、Webフォントなどのアイコンも利用できます。
[MySQL テーブル仕様書メーカー的な何か](http://mariyudu.net/mysql-doc/) の XSLT をベースにさせていただきました。

# まとめ

XML と XSLT を使用して様々なフォーマットに変換することができました。
標準では対応していない形式でもちょっとした応用をするだけで変換できそうですね。

外部キー制約とかトリガーが XML にダンプされていませんが、これは `--xml` モードの場合、`mysqlsump` 内部で `show keys from <テーブル名>` コマンドを使った出力をしているためです。
バグとして公式のバグトラッカーに起票されていますが、結構昔なので対応されることはなさそうですね（笑）[https://bugs.mysql.com/bug.php?id=66821](https://bugs.mysql.com/bug.php?id=66821)

テーブル定義書としては「片手落ち」なので、あくまでカジュアルに定義を確認したい時などに使用範囲はとどめておきましょう。