「知らぬ間によくわからないDBスキーマが追加されてるんだけど・・・」みたいなレガシー環境でDBテーブル定義をバージョン管理したい

## やりたかったこと
- DBテーブル定義をバージョン管理したい
- 修正用をレビューしたい

（今時の新規サービスですと少ないかと思いますが）
テーブル定義のマスターがDBにあるのみで定義の変更を各々の開発者が行う（他の開発者は認識しづらい）という運用になってしまっているサービスだと便利かと思います

※MySQL系でのみ動作確認済みです、最低限になるので必要に応じてカスタマイズしてください


## エクスポートコード
こちらは初回のみ実行することを想定してます
以後のテーブル定義変更は、出力されたファイルを変更していく形です

```python
"""export_database_schema.py
    データベースからCREATE TABLE文をエクスポートするのに利用します
    optional arguments:
        -h, --help            show this help message and exit
        --user USER           データベースのユーザ名
        --password PASSWORD   データベースのパスワード
        --host HOST           データベースのホスト名
        --port PORT           データベースのポート番号
        --charset CHARSET     データベースの文字コード
        --target TARGET [TARGET ...]
                              エクスポート対象のDBリスト(スペース区切り）
"""

import os
import sys
import shutil
import pymysql
import argparse

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# 引数の設定
parser = argparse.ArgumentParser()
parser.add_argument("--user", help="データベースのユーザ名", default="root")
parser.add_argument("--password", help="データベースのパスワード", default="password")
parser.add_argument("--host", help="データベースのホスト名", default="127.0.0.1")
parser.add_argument("--port", help="データベースのポート番号", default=3306)
parser.add_argument("--charset", help="データベースの文字コード", default="utf8mb4")
parser.add_argument("--target", help="エクスポート対象のDBリスト(スペース区切り）",
                    default=["users", "shops"],
                    nargs="+")
args = parser.parse_args()

print("Connection: mysql://%s:%s@%s:%s?charset=%s" % (
    args.user,
    args.password,
    args.host,
    args.port,
    args.charset))
con = pymysql.connect(
    user=args.user,
    password=args.password,
    host=args.host,
    port=args.port,
    charset=args.charset)

with con.cursor() as cursor:
    sql = "SHOW DATABASES"
    cursor.execute(sql)
    databases = cursor.fetchall()
    # 指定されたDBが存在する場合のみエクスポート処理を行う
    for d in databases:
        if d[0] not in args.target:
            continue
        # DBごとにディレクトリ作成(存在していたら一度削除)
        currentDirName = "%s/%s" % (ROOT_DIR, d[0])
        if os.path.isdir(currentDirName):
            shutil.rmtree(currentDirName)
        os.mkdir(currentDirName)
        dirname = "%s/schemas" % (currentDirName)
        os.mkdir(dirname)

        print("Export database: ", d[0])
        sql = "USE %s" % d[0]
        cursor.execute(sql)
        sql = "SHOW TABLES"
        cursor.execute(sql)
        tables = cursor.fetchall()
        for t in tables:
            print("\tExporting ", t[0])
            sql = "SHOW CREATE TABLE %s" % t
            cursor.execute(sql)
            schema = cursor.fetchone()
            filename = "%s/%s.sql" % (dirname, t[0])
            with open(filename, mode="w") as f:
                f.write(schema[1] + ";\n\n")

con.close()
```

例として、`python export_database_schema.py --target users shops`
を実行することで下記のようなディレクトリ構成でCREATE文ファイルが設置されます

```shell
├── users
│   └── schemas # users DB内のテーブルのCREATE文が格納
└── shops
    └── schemas # shops DB内のテーブルのCREATE文が格納
```

## インポートコード
```bash
#!/bin/bash

usage() {
  cat << EOS
Usage: $0 [option]
	-t TARGET	対象のテーブル定義ディレクトリ
	-d DATABASE	インポート先データベース名
	-H HOST		データベースのホスト名
	-u USER		データベースのユーザ名
	-p PASSWORD	データベースのパスワード
	-P PORT		データベースのポート番号
	-y 		実行確認をプロンプト省略
EOS
  exit 1
}

TARGET=""
DATABASE_NAME=""
HOST="127.0.0.1"
USER="root"
PASSWORD=""
PORT="3306"
AUTO_YES=false


while getopts d:t:H:u:p:P:yh OPT
do
  case $OPT in
    d) DATABASE_NAME=$OPTARG ;;
    t) TARGET=$OPTARG ;;
    H) HOST=$OPTARG ;;
    u) USER=$OPTARG ;;
    p) PASSWORD=$OPTARG ;;
    P) PORT=$OPTARG ;;
    y) AUTO_YES=true ;;
    h) usage ;;
  esac
done

ROOT_PATH=$(dirname "$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)")

if [ "${DATABASE_NAME}" == "" ]; then
  echo "インポート対象のデータベースを指定してください。"
  echo "  ./$0 -d DATABASE_NAME"
  exit 1
fi

if [ ! -d $ROOT_PATH/$TARGET/schemas ]; then
  echo "指定されたテーブル定義ディレクトリが存在しません"
  echo "${ROOT_PATH}下にディレクトリが存在するか確認してください"
  echo "  ./$0 -t TARGET"
  exit 1
fi

echo "パラメータ -------------------------"
echo "TARGET:		$TARGET"
echo "DATABASE_NAME:	$DATABASE_NAME"
echo "HOST:		$HOST"
echo "USER:		$USER"
echo "PASSWORD:		$PASSWORD"
echo "PORT:		$PORT"
echo "------------------------------------"

# 確認
if ! "${AUTO_YES}"; then
  read -p "\"$DATABASE_NAME\" データベースを初期化していいですか？(y/N)" yn
  case "$yn" in
    [yY]*) ;;
    *) exit 1;;
  esac
fi

echo "データベース初期化中..."
CMD="mysql -h$HOST"
if [ "$USER" != "" ]; then
  CMD="$CMD -u$USER"
fi
if [ "$PASSWORD" != "" ]; then
  CMD="$CMD -p$PASSWORD"
fi
if [ "$PORT" != "" ]; then
  CMD="$CMD -P$PORT"
fi

echo 'SET FOREIGN_KEY_CHECKS = 0;' > "${ROOT_PATH}/tmp.sql"
cat $ROOT_PATH/$TARGET/schemas/*.sql >> "${ROOT_PATH}/tmp.sql"

`$CMD -e "set FOREIGN_KEY_CHECKS=0; DROP DATABASE IF EXISTS $DATABASE_NAME;"`
`$CMD -e "CREATE DATABASE $DATABASE_NAME;"`
`$CMD -t $DATABASE_NAME < "${ROOT_PATH}/tmp.sql"`

rm "${ROOT_PATH}/tmp.sql"
echo "完了"

# テスト用に初期データを入れたい場合は下記のような形でファイルを配置し、一緒にインポートする
# echo "初期データ作成中..."
# `$CMD -t $DATABASE_NAME < "${ROOT_PATH}/${TARGET}/testdata/dump.sql"`
# echo "完了"

exit 0
```

インポートは
例として、`./scripts/import_database_schema.sh -t users -d users -y`
という形で行います。

実運用の際は、下記のようにテストデータを設置し、インポート時にテストデータも一緒にインポートするような形にしてgithub actionsでテストを回すような運用をしています

```shell
├── users
│   ├── testdata # users DBのテストデータを格納
│   └── schemas  # users DB内のテーブルのCREATE文が格納
└── shops
    ├── testdata # shops DBのテストデータを格納
    └── schemas  # shops DB内のテーブルのCREATE文が格納
```