#!/usr/bin/env bash # ============================================================ # apply_sql_tolerant.sh — يطبّق ملف SQL عبارة-عبارة، ويتجاوز فقط # أخطاء "الشيء موجود مسبقاً" (عمود/فهرس/جدول مكرّر) لأن هذا يعني # أن المايغريشن طُبِّقت جزئياً من قبل — أي خطأ آخر يوقف التنفيذ فوراً. # # الاستخدام: # bash apply_sql_tolerant.sh # ============================================================ set -euo pipefail CONTAINER="$1" ROOT_PW="$2" DB="$3" FILE="$4" DC="docker compose -f $(cd "$(dirname "${BASH_SOURCE[0]}")/../../docker" && pwd)/docker-compose.yml" # أكواد MySQL التي تعني "طُبِّق هذا الجزء من قبل" ولا خطر في تجاوزها: # 1060 Duplicate column 1061 Duplicate key 1091 Can't DROP (missing) # 1050 Table already exists TOLERATED='1060|1061|1091|1050' # نفصل على ';' في نهاية السطر — كل ALTER/عبارة في هذا الملف على أسطرها # الخاصة أصلاً، وما من ; داخل نص أو تعليق يكسر هذا التقسيم هنا. STATEMENT="" FAILED=0 APPLIED=0 SKIPPED=0 while IFS= read -r line; do # تجاهل التعليقات المستقلة والأسطر الفارغة قبل تجميع أي عبارة [[ "$line" =~ ^[[:space:]]*--.*$ ]] && continue [[ -z "${line// }" ]] && continue STATEMENT+="$line"$'\n' if [[ "$line" == *";" ]]; then OUT=$($DC exec -T "$CONTAINER" mysql -uroot -p"$ROOT_PW" "$DB" -e "$STATEMENT" 2>&1) && { APPLIED=$((APPLIED+1)) } || { if echo "$OUT" | grep -qE "ERROR ($TOLERATED)"; then SKIPPED=$((SKIPPED+1)) echo " … skipped (already applied): $(echo "$STATEMENT" | head -1 | cut -c1-70)" else echo "✘ FAILED on statement:" echo "$STATEMENT" echo "$OUT" FAILED=1 fi } STATEMENT="" [[ "$FAILED" == "1" ]] && exit 1 fi done < "$FILE" echo " ✔ applied=$APPLIED skipped(already-present)=$SKIPPED"