Sh3ll



Directory :  /scripts2/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

Current File : //scripts2/detect_and_convert_myisam_to_innodb_v2.sh
#!/bin/bash
#
# Detect and optionally convert MyISAM tables to InnoDB for cPanel users.
# Interactive and non-interactive modes.
#
# Suggested location:
# /scripts2/detect_and_convert_myisam_to_innodb_v2.sh
# Version : 2.0
# Date    : 2026-08-03
#
# Changes:
# - Fixed detection of databases without the cPanel username prefix.
# - Databases are now obtained from mysql.db instead of assuming "user_*".
# - Improved compatibility with migrated and restored databases.
#
# Examples:
#
# Dry-run all server:
# /scripts2/detect_and_convert_myisam_to_innodb.sh --dry-run
#
# Interactive mode:
# /scripts2/detect_and_convert_myisam_to_innodb.sh
#
# Convert all, non-interactive:
# /scripts2/detect_and_convert_myisam_to_innodb.sh --convert --pause 30 --max-load 8
#
# Convert one cPanel user:
# /scripts2/detect_and_convert_myisam_to_innodb.sh --user gesinco --convert --pause 30
#
# Convert one database:
# /scripts2/detect_and_convert_myisam_to_innodb.sh --database gesinco_site --convert --pause 30
#

set -Eeuo pipefail

export PATH="/usr/local/cpanel/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

MYSQL_BIN="$(command -v mysql || true)"

DRY_RUN=0
AUTO_CONVERT=0
FILTER_USER=""
FILTER_DATABASE=""
PAUSE_SECONDS=""
MAX_LOAD=""

show_help() {
    cat <<'EOF'
Usage:
  detect_and_convert_myisam_to_innodb.sh [options]

Options:
  --dry-run
      Detect and show MyISAM tables only. Do not ask for conversion.

  --convert
      Convert without interactive confirmation. Required for cron/Ansible mode.

  --user USERNAME
      Process only one cPanel user.

  --database DATABASE
      Process only one database.

  --pause SECONDS
      Pause between each database conversion.
      Recommended values: 15, 30, 60.

  --max-load LOAD
      If system load average is above this value, wait before processing.
      Example: --max-load 8

  --help
      Show this help.

Examples:
  ./detect_and_convert_myisam_to_innodb.sh --dry-run

  ./detect_and_convert_myisam_to_innodb.sh

  ./detect_and_convert_myisam_to_innodb.sh --convert --pause 30 --max-load 8

  ./detect_and_convert_myisam_to_innodb.sh --user gesinco --convert --pause 30

  ./detect_and_convert_myisam_to_innodb.sh --database gesinco_site --convert --pause 30
EOF
}

while [[ $# -gt 0 ]]; do
    case "$1" in
        --dry-run)
            DRY_RUN=1
            shift
            ;;
        --convert)
            AUTO_CONVERT=1
            shift
            ;;
        --user)
            FILTER_USER="${2:-}"
            shift 2
            ;;
        --database)
            FILTER_DATABASE="${2:-}"
            shift 2
            ;;
        --pause)
            PAUSE_SECONDS="${2:-}"
            shift 2
            ;;
        --max-load)
            MAX_LOAD="${2:-}"
            shift 2
            ;;
        --help|-h)
            show_help
            exit 0
            ;;
        *)
            echo "ERROR: Unknown option: $1"
            echo
            show_help
            exit 1
            ;;
    esac
done

if [[ $EUID -ne 0 ]]; then
    echo "ERROR: This script must be run as root."
    exit 1
fi

if [[ -z "$MYSQL_BIN" ]]; then
    echo "ERROR: mysql command not found."
    exit 1
fi

if [[ ! -d /var/cpanel/users ]]; then
    echo "ERROR: /var/cpanel/users not found. Is this a cPanel server?"
    exit 1
fi

if [[ ! -f /etc/userdomains ]]; then
    echo "ERROR: /etc/userdomains not found."
    echo "Try rebuilding it with:"
    echo "/scripts/updateuserdomains"
    exit 1
fi

if [[ -n "$FILTER_USER" ]] && ! [[ "$FILTER_USER" =~ ^[a-zA-Z0-9_]+$ ]]; then
    echo "ERROR: Invalid cPanel username: $FILTER_USER"
    exit 1
fi

if [[ -n "$FILTER_DATABASE" ]] && ! [[ "$FILTER_DATABASE" =~ ^[a-zA-Z0-9_\$.-]+$ ]]; then
    echo "ERROR: Invalid database name: $FILTER_DATABASE"
    exit 1
fi

if [[ -n "$PAUSE_SECONDS" ]] && ! [[ "$PAUSE_SECONDS" =~ ^[0-9]+$ ]]; then
    echo "ERROR: --pause must be a number."
    exit 1
fi

if [[ -n "$MAX_LOAD" ]] && ! [[ "$MAX_LOAD" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
    echo "ERROR: --max-load must be a number."
    exit 1
fi

mysql_query() {
    "$MYSQL_BIN" --batch --raw --skip-column-names -e "$1"
}

mysql_execute_legacy_compatible() {
    local sql_command="$1"

    "$MYSQL_BIN" -e "
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, 'NO_ZERO_DATE', '');
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, 'NO_ZERO_IN_DATE', '');
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, 'STRICT_TRANS_TABLES', '');
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, 'STRICT_ALL_TABLES', '');
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, ',,', ',');
SET SESSION sql_mode = TRIM(BOTH ',' FROM @@SESSION.sql_mode);
SET SESSION innodb_strict_mode = OFF;
${sql_command}
"
}

mysql_repair_myisam_table() {
    local db_name="$1"
    local table_name="$2"

    "$MYSQL_BIN" -e "
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, 'NO_ZERO_DATE', '');
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, 'NO_ZERO_IN_DATE', '');
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, 'STRICT_TRANS_TABLES', '');
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, 'STRICT_ALL_TABLES', '');
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, ',,', ',');
SET SESSION sql_mode = TRIM(BOTH ',' FROM @@SESSION.sql_mode);
REPAIR TABLE \`${db_name}\`.\`${table_name}\`;
"
}

sql_escape() {
    local value="$1"
    value="${value//\\/\\\\}"
    value="${value//\'/\\\'}"
    printf "%s" "$value"
}

get_domains_for_user() {
    local user="$1"

    awk -F': ' -v user="$user" '
        $2 == user {
            print $1
        }
    ' /etc/userdomains | sort -u | paste -sd "," -
}

###############################################################################
# Database helper functions
###############################################################################

get_databases_for_user() {
    local user="$1"

    mysql_query "
        SELECT DISTINCT Db
        FROM mysql.db
        WHERE User='$(sql_escape "$user")'
        ORDER BY Db;
    "
}

database_belongs_to_user() {
    local user="$1"
    local db="$2"

    mysql_query "
        SELECT 1
        FROM mysql.db
        WHERE User='$(sql_escape "$user")'
          AND Db='$(sql_escape "$db")'
        LIMIT 1;
    "
}

build_database_list() {
    local user="$1"

    if [[ -n "$FILTER_DATABASE" ]]; then
        if [[ -n "$(database_belongs_to_user "$user" "$FILTER_DATABASE")" ]]; then
            echo "$FILTER_DATABASE"
        fi
        return
    fi

    get_databases_for_user "$user"
}

wait_for_load_if_needed() {
    local context="$1"

    if [[ -z "$MAX_LOAD" ]]; then
        return 0
    fi

    while true; do
        local current_load
        current_load="$(awk '{print $1}' /proc/loadavg)"

        if awk -v current="$current_load" -v max="$MAX_LOAD" 'BEGIN { exit !(current <= max) }'; then
            return 0
        fi

        echo
        echo "Load average is $current_load, above limit $MAX_LOAD."
        echo "Waiting 60 seconds before continuing: $context"
        sleep 60
    done
}

TMP_REPORT="$(mktemp)"
TMP_DATABASES="$(mktemp)"
TMP_DATABASES_SORTED="$(mktemp)"
TMP_USERS="$(mktemp)"
TMP_RESULTS="$(mktemp)"

cleanup() {
    rm -f "$TMP_REPORT" "$TMP_DATABASES" "$TMP_DATABASES_SORTED" "$TMP_USERS" "$TMP_RESULTS"
}

trap cleanup EXIT

echo "============================================================"
echo "MyISAM detector and converter for cPanel users"
echo "Domain source: /etc/userdomains"
echo "Mode: screen output only"
echo "Legacy date defaults: supported through SESSION sql_mode"
echo "Large row format: ENGINE=InnoDB ROW_FORMAT=DYNAMIC"
echo "Crashed MyISAM tables: REPAIR TABLE fallback enabled"
echo "============================================================"
echo

if [[ "$DRY_RUN" -eq 1 ]]; then
    echo "Selected mode: DRY-RUN"
elif [[ "$AUTO_CONVERT" -eq 1 ]]; then
    echo "Selected mode: NON-INTERACTIVE CONVERSION"
else
    echo "Selected mode: INTERACTIVE"
fi

if [[ -n "$FILTER_USER" ]]; then
    echo "User filter:     $FILTER_USER"
fi

if [[ -n "$FILTER_DATABASE" ]]; then
    echo "Database filter: $FILTER_DATABASE"
fi

if [[ -n "$MAX_LOAD" ]]; then
    echo "Max load:        $MAX_LOAD"
fi

echo

echo -e "CPANEL_USER\tDOMAINS\tDATABASE\tTABLE_COUNT\tSIZE_MB" > "$TMP_REPORT"
echo -e "STATUS\tUSER\tDATABASE\tTABLE\tMESSAGE" > "$TMP_RESULTS"

if [[ -n "$FILTER_USER" ]]; then
    if [[ ! -f "/var/cpanel/users/$FILTER_USER" ]]; then
        echo "ERROR: cPanel user not found: $FILTER_USER"
        exit 1
    fi

    echo "$FILTER_USER" > "$TMP_USERS"
else
    find /var/cpanel/users -maxdepth 1 -type f -printf '%f\n' | sort > "$TMP_USERS"
fi

TOTAL_USERS="$(wc -l < "$TMP_USERS")"
CURRENT_USER=0
FOUND_USERS=0
FOUND_DATABASES=0
FOUND_TABLES=0
FOUND_SIZE_MB=0

echo "Scanning cPanel users for MyISAM tables..."
echo

while read -r CPANEL_USER; do
    [[ -z "$CPANEL_USER" ]] && continue

    CURRENT_USER=$((CURRENT_USER + 1))

    printf '[%s/%s] Checking user: %s\n' "$CURRENT_USER" "$TOTAL_USERS" "$CPANEL_USER"

    if ! [[ "$CPANEL_USER" =~ ^[a-zA-Z0-9_]+$ ]]; then
        echo "  Skipping invalid username: $CPANEL_USER"
        continue
    fi

DATABASE_LIST="$(build_database_list "$CPANEL_USER")"

if [[ -z "$DATABASE_LIST" ]]; then
    echo "  No databases found for this user."
    continue
fi

USER_FOUND_MYISAM=0

while read -r DB_NAME; do

    [[ -z "$DB_NAME" ]] && continue

    DB_ESCAPED="$(sql_escape "$DB_NAME")"

    QUERY="
SELECT
    TABLE_SCHEMA,
    COUNT(*),
    ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
  AND ENGINE = 'MyISAM'
  AND TABLE_SCHEMA = '${DB_ESCAPED}'
GROUP BY TABLE_SCHEMA
ORDER BY TABLE_SCHEMA;
"

    RESULTS="$(mysql_query "$QUERY" || true)"

    [[ -z "$RESULTS" ]] && continue

    if [[ "$USER_FOUND_MYISAM" -eq 0 ]]; then
        FOUND_USERS=$((FOUND_USERS + 1))
        USER_FOUND_MYISAM=1
    fi

    DOMAINS="$(get_domains_for_user "$CPANEL_USER")"
    [[ -z "$DOMAINS" ]] && DOMAINS="-"

    while IFS=$'\t' read -r DB_NAME TABLE_COUNT SIZE_MB; do

        [[ -z "$DB_NAME" || -z "$TABLE_COUNT" ]] && continue
        [[ -z "$SIZE_MB" || "$SIZE_MB" == "NULL" ]] && SIZE_MB=0

        FOUND_DATABASES=$((FOUND_DATABASES + 1))
        FOUND_TABLES=$((FOUND_TABLES + TABLE_COUNT))

        FOUND_SIZE_MB="$(
            awk -v a="$FOUND_SIZE_MB" -v b="$SIZE_MB" \
            'BEGIN { printf "%.2f", a+b }'
        )"

        echo -e "${CPANEL_USER}\t${DOMAINS}\t${DB_NAME}\t${TABLE_COUNT}\t${SIZE_MB}" >> "$TMP_REPORT"

        echo -e "${CPANEL_USER}\t${DB_NAME}" >> "$TMP_DATABASES"

        echo "  FOUND: $DB_NAME has $TABLE_COUNT MyISAM table(s), approx ${SIZE_MB} MB"

    done <<< "$RESULTS"

done <<< "$DATABASE_LIST"

done < "$TMP_USERS"

echo
echo "============================================================"
echo "Scan completed"
echo "============================================================"
echo "Users scanned:             $TOTAL_USERS"
echo "Users with MyISAM tables:  $FOUND_USERS"
echo "Databases with MyISAM:     $FOUND_DATABASES"
echo "Total MyISAM tables:       $FOUND_TABLES"
echo "Approx MyISAM size:        ${FOUND_SIZE_MB} MB"
echo "============================================================"
echo

if [[ "$FOUND_TABLES" -eq 0 ]]; then
    echo "No MyISAM tables found."
    exit 0
fi

echo "============================================================"
echo "Summary"
echo "============================================================"
echo

if command -v column >/dev/null 2>&1; then
    column -t -s $'\t' "$TMP_REPORT"
else
    cat "$TMP_REPORT"
fi

echo
echo "============================================================"
echo "Detailed MyISAM tables"
echo "============================================================"
echo

sort -u "$TMP_DATABASES" | while IFS=$'\t' read -r CPANEL_USER DB_NAME; do
    [[ -z "$CPANEL_USER" || -z "$DB_NAME" ]] && continue

    DOMAINS="$(get_domains_for_user "$CPANEL_USER")"
    [[ -z "$DOMAINS" ]] && DOMAINS="-"

    DB_ESCAPED="$(sql_escape "$DB_NAME")"

    echo
    echo "User:     $CPANEL_USER"
    echo "Domains:  $DOMAINS"
    echo "Database: $DB_NAME"
    echo "Tables:"

    QUERY="
SELECT
    TABLE_NAME,
    ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
  AND ENGINE = 'MyISAM'
  AND TABLE_SCHEMA = '${DB_ESCAPED}'
ORDER BY DATA_LENGTH + INDEX_LENGTH ASC, TABLE_NAME;
"

    mysql_query "$QUERY" | awk -F'\t' '{print "  - " $1 " (" $2 " MB)"}'
done

if [[ "$DRY_RUN" -eq 1 ]]; then
    echo
    echo "============================================================"
    echo "Dry-run finished"
    echo "============================================================"
    echo "No changes were made."
    echo "============================================================"
    exit 0
fi

if [[ "$AUTO_CONVERT" -eq 0 && ! -t 0 ]]; then
    echo
    echo "============================================================"
    echo "Non-interactive input detected"
    echo "============================================================"
    echo "No --convert option was provided, so no conversion will be performed."
    echo "Use --convert --pause 30 for cron or Ansible mode."
    echo "============================================================"
    exit 0
fi

echo
echo "============================================================"
echo "IMPORTANT"
echo "============================================================"
echo "- Make sure you have recent backups before converting tables."
echo "- Large tables may lock or impact busy websites during conversion."
echo "- Tables with old date defaults like '0000-00-00 00:00:00' will be handled using SESSION sql_mode."
echo "- Tables will be converted using ENGINE=InnoDB ROW_FORMAT=DYNAMIC."
echo "- This does NOT change the global MySQL/MariaDB sql_mode."
echo "- This does NOT change the global MySQL/MariaDB innodb_strict_mode."
echo "- If a MyISAM table is marked as crashed, the script will try REPAIR TABLE and retry conversion."
echo "- The script will pause between each database conversion."
echo

if [[ "$AUTO_CONVERT" -eq 0 ]]; then
    read -r -p "Do you want to continue with the conversion to InnoDB? Type CONVERT to continue: " CONFIRM

    if [[ "$CONFIRM" != "CONVERT" ]]; then
        echo
        echo "Conversion aborted. No changes were made."
        exit 0
    fi
fi

if [[ -z "$PAUSE_SECONDS" ]]; then
    if [[ "$AUTO_CONVERT" -eq 1 ]]; then
        PAUSE_SECONDS=30
        echo "No --pause provided. Using default pause: ${PAUSE_SECONDS}s"
    else
        echo
        echo "Choose pause between each database conversion:"
        echo "1) 15 seconds"
        echo "2) 30 seconds"
        echo "3) 60 seconds"
        echo

        read -r -p "Enter option [1/2/3]: " PAUSE_OPTION

        case "$PAUSE_OPTION" in
            1) PAUSE_SECONDS=15 ;;
            2) PAUSE_SECONDS=30 ;;
            3) PAUSE_SECONDS=60 ;;
            *)
                echo "Invalid option. Aborting."
                exit 1
                ;;
        esac
    fi
fi

sort -u "$TMP_DATABASES" > "$TMP_DATABASES_SORTED"

TOTAL_DATABASES="$(wc -l < "$TMP_DATABASES_SORTED")"
CURRENT_DATABASE=0
CONVERTED_DATABASES=0
FAILED_DATABASES=0
CONVERTED_TABLES=0
FAILED_TABLES=0
REPAIRED_TABLES=0
SKIPPED_TABLES=0

echo
echo "============================================================"
echo "Starting conversion"
echo "============================================================"
echo "Pause between databases: ${PAUSE_SECONDS}s"
echo "Databases to process:    $TOTAL_DATABASES"
echo "Legacy date mode:        enabled per MySQL session"
echo "InnoDB row format:       DYNAMIC"
echo "Repair fallback:         enabled for crashed MyISAM tables"
if [[ -n "$MAX_LOAD" ]]; then
    echo "Max load:                $MAX_LOAD"
fi
echo "============================================================"
echo

while IFS=$'\t' read -r CPANEL_USER DB_NAME; do
    [[ -z "$CPANEL_USER" || -z "$DB_NAME" ]] && continue

    CURRENT_DATABASE=$((CURRENT_DATABASE + 1))

    wait_for_load_if_needed "database $DB_NAME"

    DOMAINS="$(get_domains_for_user "$CPANEL_USER")"
    [[ -z "$DOMAINS" ]] && DOMAINS="-"

    DB_ESCAPED="$(sql_escape "$DB_NAME")"

    echo
    echo "------------------------------------------------------------"
    echo "[$CURRENT_DATABASE/$TOTAL_DATABASES] Processing database"
    echo "User:     $CPANEL_USER"
    echo "Domains:  $DOMAINS"
    echo "Database: $DB_NAME"
    echo "Started:  $(date)"
    echo "------------------------------------------------------------"

    TMP_SQL="$(mktemp)"

    QUERY="
SELECT CONCAT(
    'ALTER TABLE \`',
    REPLACE(TABLE_SCHEMA, '\`', '\`\`'),
    '\`.\`',
    REPLACE(TABLE_NAME, '\`', '\`\`'),
    '\` ENGINE=InnoDB ROW_FORMAT=DYNAMIC;'
)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
  AND ENGINE = 'MyISAM'
  AND TABLE_SCHEMA = '${DB_ESCAPED}'
ORDER BY DATA_LENGTH + INDEX_LENGTH ASC, TABLE_NAME;
"

    mysql_query "$QUERY" > "$TMP_SQL" || true

    if [[ ! -s "$TMP_SQL" ]]; then
        echo "No MyISAM tables currently found in $DB_NAME. Skipping."
        echo -e "SKIPPED\t${CPANEL_USER}\t${DB_NAME}\t-\tNo MyISAM tables currently found" >> "$TMP_RESULTS"
        rm -f "$TMP_SQL"
        continue
    fi

    TABLES_IN_DB="$(wc -l < "$TMP_SQL")"
    CURRENT_TABLE=0
    DB_FAILED=0

    while read -r SQL_COMMAND; do
        [[ -z "$SQL_COMMAND" ]] && continue

        CURRENT_TABLE=$((CURRENT_TABLE + 1))

        wait_for_load_if_needed "table $CURRENT_TABLE/$TABLES_IN_DB in database $DB_NAME"

        TABLE_NAME="$(echo "$SQL_COMMAND" | sed -E 's/^ALTER TABLE `[^`]+`\.`([^`]+)` ENGINE=InnoDB( ROW_FORMAT=DYNAMIC)?;$/\1/')"

        echo
        echo "  [$CURRENT_TABLE/$TABLES_IN_DB] Converting table: $TABLE_NAME"
        echo "  SQL: $SQL_COMMAND"
        echo "  Using legacy-compatible SESSION sql_mode, innodb_strict_mode=OFF and ROW_FORMAT=DYNAMIC."

        CONVERT_OUTPUT="$(mktemp)"

        if mysql_execute_legacy_compatible "$SQL_COMMAND" > "$CONVERT_OUTPUT" 2>&1; then
            echo "  OK: $TABLE_NAME converted to InnoDB."
            CONVERTED_TABLES=$((CONVERTED_TABLES + 1))
            echo -e "OK\t${CPANEL_USER}\t${DB_NAME}\t${TABLE_NAME}\tConverted to InnoDB" >> "$TMP_RESULTS"
        else
            cat "$CONVERT_OUTPUT"

            if grep -qiE "marked as crashed|should be repaired|is crashed" "$CONVERT_OUTPUT"; then
                echo
                echo "  WARNING: Table appears to be crashed: $TABLE_NAME"
                echo "  Attempting REPAIR TABLE before retrying conversion..."

                REPAIR_OUTPUT="$(mktemp)"

                if mysql_repair_myisam_table "$DB_NAME" "$TABLE_NAME" > "$REPAIR_OUTPUT" 2>&1; then
                    cat "$REPAIR_OUTPUT"
                    echo "  OK: REPAIR TABLE completed for $TABLE_NAME"
                    echo "  Retrying conversion: $TABLE_NAME"

                    if mysql_execute_legacy_compatible "$SQL_COMMAND"; then
                        echo "  OK: $TABLE_NAME converted to InnoDB after repair."
                        CONVERTED_TABLES=$((CONVERTED_TABLES + 1))
                        REPAIRED_TABLES=$((REPAIRED_TABLES + 1))
                        echo -e "REPAIRED\t${CPANEL_USER}\t${DB_NAME}\t${TABLE_NAME}\tRepaired and converted to InnoDB" >> "$TMP_RESULTS"
                    else
                        echo "  ERROR: Failed converting table after repair: $TABLE_NAME"
                        DB_FAILED=1
                        FAILED_TABLES=$((FAILED_TABLES + 1))
                        echo -e "FAILED\t${CPANEL_USER}\t${DB_NAME}\t${TABLE_NAME}\tFailed after REPAIR TABLE" >> "$TMP_RESULTS"
                    fi
                else
                    cat "$REPAIR_OUTPUT"
                    echo "  ERROR: REPAIR TABLE failed for $TABLE_NAME"
                    echo "  Manual repair may be required."
                    DB_FAILED=1
                    FAILED_TABLES=$((FAILED_TABLES + 1))
                    echo -e "FAILED\t${CPANEL_USER}\t${DB_NAME}\t${TABLE_NAME}\tREPAIR TABLE failed" >> "$TMP_RESULTS"
                fi

                rm -f "$REPAIR_OUTPUT"
            else
                echo "  ERROR: Failed converting table: $TABLE_NAME"

                if grep -qiE "Row size too large|ERROR 1118" "$CONVERT_OUTPUT"; then
                    echo "  Hint: This looks like ERROR 1118 / Row size too large."
                    echo "  The script already used ROW_FORMAT=DYNAMIC and innodb_strict_mode=OFF."
                    echo "  Manual review may be needed to convert some large VARCHAR/CHAR columns to TEXT."
                    echo -e "FAILED\t${CPANEL_USER}\t${DB_NAME}\t${TABLE_NAME}\tERROR 1118 Row size too large" >> "$TMP_RESULTS"
                elif grep -qiE "Invalid default value|ERROR 1067" "$CONVERT_OUTPUT"; then
                    echo "  Hint: This looks like ERROR 1067 / Invalid default value."
                    echo "  The script already used a legacy-compatible SESSION sql_mode."
                    echo "  Manual review may be needed if the table still fails."
                    echo -e "FAILED\t${CPANEL_USER}\t${DB_NAME}\t${TABLE_NAME}\tERROR 1067 Invalid default value" >> "$TMP_RESULTS"
                else
                    echo "  Hint: Check the MySQL error above for the exact cause."
                    echo -e "FAILED\t${CPANEL_USER}\t${DB_NAME}\t${TABLE_NAME}\tConversion failed" >> "$TMP_RESULTS"
                fi

                DB_FAILED=1
                FAILED_TABLES=$((FAILED_TABLES + 1))
            fi
        fi

        rm -f "$CONVERT_OUTPUT"

    done < "$TMP_SQL"

    rm -f "$TMP_SQL"

    if [[ "$DB_FAILED" -eq 0 ]]; then
        CONVERTED_DATABASES=$((CONVERTED_DATABASES + 1))
        echo
        echo "OK: Database completed successfully: $DB_NAME"
    else
        FAILED_DATABASES=$((FAILED_DATABASES + 1))
        echo
        echo "WARNING: Database completed with errors: $DB_NAME"
    fi

    echo "Finished: $(date)"

    if [[ "$CURRENT_DATABASE" -lt "$TOTAL_DATABASES" ]]; then
        echo
        echo "Sleeping ${PAUSE_SECONDS}s before next database..."
        sleep "$PAUSE_SECONDS"
    fi

done < "$TMP_DATABASES_SORTED"

echo
echo "============================================================"
echo "Conversion finished"
echo "============================================================"
echo "Databases processed:       $TOTAL_DATABASES"
echo "Tables converted:          $CONVERTED_TABLES"
echo "Tables repaired+converted: $REPAIRED_TABLES"
echo "Tables failed:             $FAILED_TABLES"
echo "Tables skipped:            $SKIPPED_TABLES"
echo "Completed databases:       $CONVERTED_DATABASES"
echo "Databases with errors:     $FAILED_DATABASES"
echo "Finished at:               $(date)"
echo "============================================================"
echo

echo "============================================================"
echo "Final result detail"
echo "============================================================"

if command -v column >/dev/null 2>&1; then
    column -t -s $'\t' "$TMP_RESULTS"
else
    cat "$TMP_RESULTS"
fi

echo
echo "============================================================"
echo "Remaining MyISAM tables after execution"
echo "============================================================"

REMAINING_QUERY="
SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
  AND ENGINE = 'MyISAM'
ORDER BY TABLE_SCHEMA, TABLE_NAME;
"

REMAINING_MYISAM="$(mysql_query "$REMAINING_QUERY" || true)"

if [[ -z "$REMAINING_MYISAM" ]]; then
    echo "No MyISAM tables remain on the server."
else
    echo "$REMAINING_MYISAM" | awk -F'\t' '{print $1 "." $2 " (" $3 ")"}'
fi

echo "============================================================"


Sh3LL