Files
openclaw-config/workspace-sql/skills/table-alias/sync_tables.sh
T

345 lines
12 KiB
Bash
Executable File

#!/bin/bash
# ============================================================
# 表结构对齐脚本 - 准生产 → VPS
# ============================================================
# 用法:./sync_tables.sh <source_database> [target_database]
# 示例:./sync_tables.sh dmp_serp
# ./sync_tables.sh dmp_smdm dmp_smdm
# ============================================================
# 去掉 set -e,允许错误继续执行
# 加载配置
CONFIG_FILE="/home/yangxuan/.openclaw/workspace-sql/.db-sync-config.yaml"
SCRIPT_DIR="/home/yangxuan/codes/xuan-sql/scripts"
MEMORY_DIR="/home/yangxuan/.openclaw/workspace-sql/memory"
# 解析参数
SOURCE_DB="${1:-}"
TARGET_DB="${2:-$SOURCE_DB}"
if [ -z "$SOURCE_DB" ]; then
echo "用法:$0 <source_database> [target_database]"
echo "示例:$0 dmp_serp"
exit 1
fi
# 从配置文件读取连接信息(简单 YAML 解析)
SOURCE_HOST=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "host:" | head -1 | awk '{print $2}')
SOURCE_PORT=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "port:" | head -1 | awk '{print $2}')
SOURCE_USER=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "user:" | head -1 | awk '{print $2}')
SOURCE_PWD_ENV=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "password_env:" | head -1 | awk '{print $2}')
TARGET_HOST=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "host:" | head -1 | awk '{print $2}')
TARGET_PORT=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "port:" | head -1 | awk '{print $2}')
TARGET_USER=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "user:" | head -1 | awk '{print $2}')
TARGET_PWD_ENV=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "password_env:" | head -1 | awk '{print $2}')
# 获取密码
SOURCE_PWD="${!SOURCE_PWD_ENV}"
TARGET_PWD="${!TARGET_PWD_ENV}"
if [ -z "$SOURCE_PWD" ] || [ -z "$TARGET_PWD" ]; then
echo "错误:密码环境变量未设置"
echo "请确保已设置:$SOURCE_PWD_ENV$TARGET_PWD_ENV"
exit 1
fi
# 生成输出目录
DATE_STR=$(date +%Y%m%d)
OUTPUT_DIR="$SCRIPT_DIR/$DATE_STR"
mkdir -p "$OUTPUT_DIR"
echo "============================================================"
echo "表结构对齐 - 准生产 → VPS"
echo "============================================================"
echo "源数据库:$SOURCE_HOST:$SOURCE_PORT / $SOURCE_DB"
echo "目标数据库:$TARGET_HOST:$TARGET_PORT / $TARGET_DB"
echo "输出目录:$OUTPUT_DIR"
echo "============================================================"
# 验证连接
echo ""
echo "[1/5] 验证数据库连接..."
# 验证源库
if ! mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -e "SELECT 1;" > /dev/null 2>&1; then
echo "错误:无法连接到源数据库(准生产)"
exit 1
fi
echo "✓ 源数据库连接成功"
# 验证目标库
if ! mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -e "SELECT 1;" > /dev/null 2>&1; then
echo "错误:无法连接到目标数据库(VPS)"
exit 1
fi
echo "✓ 目标数据库连接成功"
# 获取表列表
echo ""
echo "[2/5] 获取源数据库表列表..."
TABLES=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "SHOW TABLES;" 2>/dev/null)
TABLE_COUNT=$(echo "$TABLES" | wc -l)
echo "找到 $TABLE_COUNT 张表"
if [ "$TABLE_COUNT" -gt 50 ]; then
read -p "表数量超过 50 张(当前:$TABLE_COUNT),是否继续?[y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "已取消"
exit 0
fi
fi
# 初始化统计
SUCCESS_COUNT=0
FAIL_COUNT=0
declare -a TABLE_STATUS=()
# 处理每个表
echo ""
echo "[3/5] 对比并生成脚本..."
for TABLE in $TABLES; do
echo ""
echo "处理表:$TABLE"
SCRIPT_FILE="$OUTPUT_DIR/${TARGET_DB}__${TABLE}__struct.sql"
# 获取源表结构
SOURCE_DDL=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "SHOW CREATE TABLE $TABLE\G" 2>/dev/null)
# 检查目标表是否存在
TARGET_EXISTS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "SHOW TABLES LIKE '$TABLE';" 2>/dev/null | wc -l)
if [ "$TARGET_EXISTS" -eq 0 ]; then
# 目标表不存在,创建表
echo " → 目标表不存在,执行 CREATE TABLE"
# 提取 CREATE TABLE 语句
CREATE_STMT=$(echo "$SOURCE_DDL" | grep -A1000 "Create Table:" | tail -n +2 | sed 's/^ //')
# 生成脚本
cat > "$SCRIPT_FILE" << EOF
-- ============================================================
-- 表结构同步脚本:$TABLE
-- 源:准生产 ($SOURCE_HOST:$SOURCE_PORT)
-- 目标:VPS ($TARGET_HOST:$TARGET_PORT) $TARGET_DB
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
-- 操作:CREATE TABLE(目标表不存在)
-- ============================================================
USE $TARGET_DB;
$CREATE_STMT
-- ============================================================
-- 验证
-- ============================================================
-- SHOW CREATE TABLE $TABLE\\G
EOF
# 执行脚本
if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" < "$SCRIPT_FILE" 2>/dev/null; then
echo " ✓ CREATE TABLE 成功"
((SUCCESS_COUNT++))
TABLE_STATUS+=("| $TABLE | CREATE | ✅ | 新建表 |")
else
echo " ✗ CREATE TABLE 失败"
((FAIL_COUNT++))
TABLE_STATUS+=("| $TABLE | CREATE | ❌ | 创建失败 |")
fi
else
# 目标表已存在,对比结构
echo " → 目标表已存在,对比结构差异..."
TARGET_DDL=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "SHOW CREATE TABLE $TABLE\G" 2>/dev/null)
# 获取字段列表对比
SOURCE_FIELDS=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), IFNULL(COLUMN_COMMENT,'')
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '$SOURCE_DB' AND TABLE_NAME = '$TABLE'
ORDER BY ORDINAL_POSITION;
" 2>/dev/null)
TARGET_FIELDS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), IFNULL(COLUMN_COMMENT,'')
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '$TARGET_DB' AND TABLE_NAME = '$TABLE'
ORDER BY ORDINAL_POSITION;
" 2>/dev/null)
# 生成 ALTER 语句(简化版:只处理字段增删改)
ALTER_STMT=""
CHANGES=""
# 检查源有目标无的字段(添加)
while IFS='|' read -r COL_NAME COL_TYPE IS_NULL COL_DEF COL_COMMENT; do
if ! echo "$TARGET_FIELDS" | grep -q "^$COL_NAME|"; then
# 字段不存在,添加
NULLABLE="$([ "$IS_NULL" = "YES" ] && echo "NULL" || echo "NOT NULL")"
DEFAULT="$([ "$COL_DEF" != "NULL" ] && echo "DEFAULT $COL_DEF" || echo "")"
COMMENT="$([ -n "$COL_COMMENT" ] && echo "COMMENT '$COL_COMMENT'" || echo "")"
ALTER_STMT="$ALTER_STMT
ALTER TABLE $TABLE ADD COLUMN $COL_NAME $COL_TYPE $NULLABLE $DEFAULT $COMMENT;"
CHANGES="$CHANGES\n - 添加字段:$COL_NAME"
fi
done <<< "$SOURCE_FIELDS"
# 检查目标有源无的字段(删除)
while IFS='|' read -r COL_NAME COL_TYPE IS_NULL COL_DEF COL_COMMENT; do
if ! echo "$SOURCE_FIELDS" | grep -q "^$COL_NAME|"; then
# 源不存在,删除
ALTER_STMT="$ALTER_STMT
ALTER TABLE $TABLE DROP COLUMN $COL_NAME;"
CHANGES="$CHANGES\n - 删除字段:$COL_NAME"
fi
done <<< "$TARGET_FIELDS"
if [ -n "$ALTER_STMT" ]; then
echo " → 发现结构差异,生成 ALTER 语句"
# 生成脚本
cat > "$SCRIPT_FILE" << EOF
-- ============================================================
-- 表结构同步脚本:$TABLE
-- 源:准生产 ($SOURCE_HOST:$SOURCE_PORT)
-- 目标:VPS ($TARGET_HOST:$TARGET_PORT) $TARGET_DB
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
-- 操作:ALTER TABLE(结构差异)
-- ============================================================
USE $TARGET_DB;
-- 结构差异:$(echo -e "$CHANGES")
$ALTER_STMT
-- ============================================================
-- 验证
-- ============================================================
-- SHOW CREATE TABLE $TABLE\\G
EOF
# 执行脚本(失败继续)
if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" < "$SCRIPT_FILE" 2>/dev/null; then
echo " ✓ ALTER TABLE 成功"
((SUCCESS_COUNT++))
TABLE_STATUS+=("| $TABLE | ALTER | ✅ | $(echo -e "$CHANGES" | wc -l) 处变更 |")
else
echo " ✗ ALTER TABLE 失败(继续执行下一个表)"
((FAIL_COUNT++))
TABLE_STATUS+=("| $TABLE | ALTER | ❌ | 执行失败 |")
fi
else
echo " ✓ 结构一致,无需修改"
((SUCCESS_COUNT++))
TABLE_STATUS+=("| $TABLE | SKIP | ✅ | 结构一致 |")
rm -f "$SCRIPT_FILE" # 删除空脚本
fi
fi
done
# 生成 README
echo ""
echo "[4/5] 生成 README.md..."
cat > "$OUTPUT_DIR/README.md" << EOF
# 表结构同步脚本 - $DATE_STR
**源:** 准生产 \`$SOURCE_HOST:$SOURCE_PORT\` \`$SOURCE_DB\`
**目标:** VPS \`$TARGET_HOST:$TARGET_PORT\` \`$TARGET_DB\`
**生成时间:** $(date '+%Y-%m-%d %H:%M:%S')
**执行状态:** $([ $FAIL_COUNT -eq 0 ] && echo "✅ 已完成" || echo "⚠️ 部分失败")
## 脚本清单
| 序号 | 脚本文件 | 操作类型 | 状态 | 说明 |
|------|---------|---------|------|------|
EOF
i=1
for STATUS in "${TABLE_STATUS[@]}"; do
TABLE_NAME=$(echo "$STATUS" | cut -d'|' -f2 | tr -d ' ')
SCRIPT_NAME="${TARGET_DB}__${TABLE_NAME}__struct.sql"
echo "| $i | \`$SCRIPT_NAME\` $STATUS" >> "$OUTPUT_DIR/README.md"
((i++))
done
cat >> "$OUTPUT_DIR/README.md" << EOF
## 执行结果
- 总表数:$TABLE_COUNT
- 成功:$SUCCESS_COUNT
- 失败:$FAIL_COUNT
## 验证命令
\`\`\`sql
-- 检查表列表
SHOW TABLES LIKE '$TARGET_DB%';
-- 检查表结构
SHOW CREATE TABLE <table_name>\\G
\`\`\`
EOF
# 记录到记忆文件
echo ""
echo "[5/5] 记录到记忆文件..."
MEMORY_FILE="$MEMORY_DIR/$(date +%Y-%m-%d).md"
if [ ! -f "$MEMORY_FILE" ]; then
echo "# $(date +%Y-%m-%d) 工作记录" > "$MEMORY_FILE"
echo "" >> "$MEMORY_FILE"
fi
cat >> "$MEMORY_FILE" << EOF
## 表结构同步 - $(date '+%H:%M')
**源:** 准生产 \`$SOURCE_DB\`
**目标:** VPS \`$TARGET_DB\`
**处理表数:** $TABLE_COUNT
**成功:** $SUCCESS_COUNT
**失败:** $FAIL_COUNT
### 处理的表
| 表名 | 操作 | 状态 | 说明 |
|------|------|------|------|
EOF
for STATUS in "${TABLE_STATUS[@]}"; do
echo "$STATUS" >> "$MEMORY_FILE"
done
cat >> "$MEMORY_FILE" << EOF
### 脚本位置
\`\`\`
$OUTPUT_DIR/
├── README.md
└── ${TARGET_DB}__*_struct.sql
\`\`\`
EOF
echo ""
echo "============================================================"
echo "执行完成!"
echo "============================================================"
echo "总表数:$TABLE_COUNT"
echo "成功:$SUCCESS_COUNT"
echo "失败:$FAIL_COUNT"
echo "脚本目录:$OUTPUT_DIR"
echo "记忆文件:$MEMORY_FILE"
echo "============================================================"