やりたかったこと
- DBテーブル定義をバージョン管理したい
- 修正用をレビューしたい
(今時の新規サービスですと少ないかと思いますが)
テーブル定義のマスターがDBにあるのみで定義の変更を各々の開発者が行う(他の開発者は認識しづらい)という運用になってしまっているサービスだと便利かと思います
※MySQL系でのみ動作確認済みです、最低限になるので必要に応じてカスタマイズしてください
エクスポートコード
こちらは初回のみ実行することを想定してます
以後のテーブル定義変更は、出力されたファイルを変更していく形です
"""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文ファイルが設置されます
├── users
│ └── schemas # users DB内のテーブルのCREATE文が格納
└── shops
└── schemas # shops DB内のテーブルのCREATE文が格納
インポートコード
#!/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でテストを回すような運用をしています
├── users
│ ├── testdata # users DBのテストデータを格納
│ └── schemas # users DB内のテーブルのCREATE文が格納
└── shops
├── testdata # shops DBのテストデータを格納
└── schemas # shops DB内のテーブルのCREATE文が格納