Macで作成したSQLファイルをサーバー上のMySQLへ反映するとき、SQLを転送してSSHでログインしてからmysqlを実行する、という作業を毎回やるのは面倒。

SSHの標準入力へSQLをそのまま流せば、Macから直接実行できる。

1、MySQLの接続情報をサーバー側に保存

複数のデータベースを使う場合は、サーバー側の ~/.my.cnf に接続情報をグループ別に登録しておく。

[client-project1]
user=example_user1
password=PASSWORD1
database=example_database1

[client-project2]
user=example_user2
password=PASSWORD2
database=example_database2

パスワードが入っているので権限を絞る。

chmod 600 ~/.my.cnf

接続確認。

mysql --defaults-group-suffix=-project1
2、MacからSQLを直接流す

Mac側のSQLファイルを、SSH経由でサーバー側のmysqlへ直接渡す。

ssh example-server 'mysql --defaults-group-suffix=-project1' < project1_update.sql

これならSQLファイルをサーバーへ転送しておく必要はない。

3、どうせならコマンド一発にする

さらに、Downloadsにある最新のSQLを自動検出し、ファイル名から投入先を判定、実行前に確認し、成功したSQLだけ別フォルダへ退避するようにした。

実環境固有のファイル名や接続先はローカル設定ファイルへ分離しておけば、スクリプト本体にはサーバー情報を持たせなくて済む。

フルコードは以下。

# deploysql.zsh
# Upload the newest matching SQL file in ~/Downloads directly to a remote MySQL client over SSH.
# Environment-specific mappings live in ~/.config/deploysql/config.zsh and should not be committed.

deploysql() {
    local dir="$HOME/Downloads"
    local archive="$HOME/Desktop/deployed_sql"
    local config="$HOME/.config/deploysql/config.zsh"
    local file base target timestamp dest answer pattern

    if [[ ! -r "$config" ]]; then
        echo "設定ファイルがありません: $config"
        echo "deploysql_config.example.zsh を参考に作成してください。"
        return 1
    fi

    # shellcheck disable=SC1090
    source "$config"

    if [[ -z "$DEPLOYSQL_SSH_HOST" || ${#DEPLOYSQL_RULES[@]} -eq 0 ]]; then
        echo "deploysql の設定が不完全です: $config"
        return 1
    fi

    # Build find conditions from configured filename patterns.
    local -a find_args
    find_args=("$dir" -maxdepth 1 -type f '(')
    local first=1
    for pattern target in ${(kv)DEPLOYSQL_RULES}; do
        if (( ! first )); then
            find_args+=(-o)
        fi
        find_args+=(-name "$pattern")
        first=0
    done
    find_args+=(')' -print0)

    file=$(find "${find_args[@]}" | xargs -0 ls -t 2>/dev/null | head -1)

    if [[ -z "$file" ]]; then
        echo "投入対象のSQLがありません。"
        return 1
    fi

    base="${file:t}"
    target=""

    for pattern target_name in ${(kv)DEPLOYSQL_RULES}; do
        if [[ "$base" == ${~pattern} ]]; then
            target="$target_name"
            break
        fi
    done

    if [[ -z "$target" ]]; then
        echo "投入先を判定できません: $base"
        return 1
    fi

    echo
    echo "----------------------------------------"
    echo " SQL : $base"
    echo " DB  : $target"
    echo " HOST: $DEPLOYSQL_SSH_HOST"
    echo "----------------------------------------"
    echo

    read "answer?このSQLを投入しますか? [y/N] "
    if [[ "$answer" != [yY] ]]; then
        echo "キャンセルしました。"
        return 0
    fi

    if ssh "$DEPLOYSQL_SSH_HOST" "mysql --defaults-group-suffix=-$target" < "$file"; then
        echo
        echo "DBへの投入に成功しました。"

        mkdir -p "$archive"
        timestamp=$(date '+%Y%m%d_%H%M%S')
        dest="$archive/${timestamp}_${base}"
        mv "$file" "$dest"

        echo "SQLを退避しました:"
        echo "$dest"
    else
        echo
        echo "ERROR: DBへの投入に失敗しました。"
        echo "SQLはDownloadsに残しています。"
        return 1
    fi
}

Macの ~/.zshrc から読み込む。

source "$HOME/git/samples/deploysql.zsh"

実環境用の設定は例えば ~/.config/deploysql/config.zsh に置き、Gitでは管理しない。

DEPLOYSQL_SSH_HOST="example-server"

typeset -A DEPLOYSQL_RULES
DEPLOYSQL_RULES=(
    'project1_*.sql' 'project1'
    'project2_*.sql' 'project2'
)
4、あとはdeploysqlだけ

SQLをDownloadsへ保存したら、

deploysql

だけ。

対象SQLと投入先を表示して確認し、問題なければ y。実行に成功したSQLだけ ~/Desktop/deployed_sql/ へ日時付きで移動する。失敗した場合はDownloadsに残るので、修正して再実行できる。

毎回やっている短い作業ほど、コマンド一発にしてしまうと楽。