#!/bin/bash
#
# Restores a MySQL/MariaDB database from a native or dockerized RDBMS instance
# accessible from this box. Also creates an owner user for this database
# (if it doesn't exist) and grants the appropriate privileges.
#
# Needs MySQL v5.7.6 or MariaDB 10.1.3 (or later).
# To restore a database with the necessary user management and grants,
# needs the superuser privileges on RDBMS.
# * If the RDBMS runs dockerized you need call this script as a Docker manager
#  user (member of the docker Linux group).
# * If we're using a native MySQL/MariaDB, you need call this script as a
#   Linux user whom the superuser role has been already granted within RDBMS
#   (via unix_socket authentication) or you need provide the superuser's
#   credentials as well.
# Lack of this the script will skip the user management and grant steps.
#
# Usage:
# $0 [-U dbuser] [-P dbpass] [-h dbhost] [-p dbport]
#    [-A dbadminuser] [-a dbadminpass] [-c characterset]
#    [-C container] [-d database] [-f dumpfile ]
#    [database (if not in -d)] [dumpfile (if not in -f)]
#
# Author: Kovács Zoltán <kovacsz@marcusconsulting.hu>
#         Kovács Zoltán <kovacs.zoltan@smartfront.hu>
# License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html)
# 2025-09-01 v1.2
# fix: Decimal conversion has been forced in MySQL/MariaDB version comparison.
# 2025-03-04 v1.1
# new: Works with dockerized databases but hasn't yet been tested with natives.
# mod: Database user creation and grants rewritten. Now create user @'%'
#      (instead of @'myhost') if it doesn't already exist.
# 2023-06-18 v1.0
# new: forked from the "SMARTERP_skeleton" repository.
# 2022-04-07 v0.4
# new: An option and a guess mechanism has added to set the default character set
#      of the restored database.
# 2021-08-30 v0.3
# fix: Uses the defaults when MYDBA* variables aren't set.
# 2021-03-22 v0.2
# fix: A duplicated SQLVERB has blocked setting password for a newly created user.
#      The unnecessary PASSWORD() call has been removed as well.
# fix: Typos.
# 2021-02-18 v0.1 Initial release

# Accepted environment variables and their defaults.
#
MYCONTAINER=${MYCONTAINER-""}                   # Docker container's name
MYCHARSET=${MYCHARSET-""}                       # Default character set for DB
MYDATABASE=${MYDATABASE-""}                     # Database name to restore
MYDBAUSER=${MYDBAUSER:-""}			# Database admin superuser
MYDBAPASSWORD=${MYDBAPASSWORD:-""}		# Credential for the DBA user
MYDUMP=${MYDUMP-""}                             # Dump file pathname
MYHOST=${MYHOST:-"localhost"}                   # Connection parameter
MYPASSWORD=${MYPASSWORD-""}                     # Credential for the DB owner
MYPORT=${MYPORT:-"3306"}                        # Connection parameter
MYUSER=${MYUSER:-"root"}			# Owner of the restored DB

# Basic environment settings.
#
LANG=C
LC_ALL=C
# We need also the sbin directories.
if ! [[ "$PATH" =~ '/sbin:' ]]; then
PATH="$PATH:/usr/local/sbin:/usr/sbin:/sbin"; fi

# Other initialisations.
#
LOGSTAMP="\"\$DATE\" +%Y-%m-%d\ %H:%M:%S"	# Timestamp format for logs
MARIADBMIN="10#010001003"			# MariaDB minimum version
MYSQLMIN="10#005007006"				# MySQL minimum version
vetodatabases="information_schema mysql performance_schema sys"

# Messages.
#
MSG_AMBIGOUS="The character set used within the dump is ambigous."
MSG_BADDBTYPE="Unknown database type"
MSG_BADDUMP="Doesn't exist or doesn't a dumpfile:"
MSG_BADOPT="Invalid option"
MSG_BADPARAM="Doubtful parameter:"
MSG_BLOCKING="This is a fatal error - restore has been aborted."
MSG_CONNTERM="DB connection(s) have forced to terminate"
MSG_DOCKERGRPNEED="You must be a member of the docker group."
MSG_DOESNOTRUN="Doesn't run the database container"
MSG_EXISTING="did not create exisiting object"
MSG_FAILCONN="Failed to connect the RDBMS."
MSG_FAILGRANT="Failet to grant privileges to user"
MSG_FAILKILL="Failed to kill active connection"
MSG_FAILPASS="Failed to set password to user"
MSG_FAILTOKILL="Failed to retrieve the active connections."
MSG_FAILVER="Failed to get the RDBMS version."
MSG_FAILUSER="Failed to create RDBMS user"
MSG_MISSINGDEP="Fatal: missing dependency"
MSG_NONBLOCKING="Recoverable error - restore is continuing."
MSG_NONSUPER="DB user hasn't DBA (database superuser) privileges."
MSG_NONZERO="The result code is non zero"
MSG_OLDRDBMS="RDBMS version is too old"
MSG_PERCENT="Hint: you may use percent-encoding (e.g %40 instead of @)"
MSG_SUPERNEED="user must have DBA (database superuser) privileges."

MSG_USAGE="Usage: $0 [options] [database [dump_pathname]]\n"
MSG_USAGE+="Option:\tENVVAR:\n"
MSG_USAGE+=" -A\tMYDBAUSER     \tMySQL/MariaDB DB admin superuser\n"
MSG_USAGE+=" -a\tMYDBAPASSWORD \tMySQL/MariaDB DB admin password\n"
MSG_USAGE+=" -c\tMYCHARSET     \tMySQL/MariaDB DB character set\n"
MSG_USAGE+=" -C\tMYCONTAINER   \tMySQL/MariaDB Docker container's name\n"
MSG_USAGE+=" -d\tMYDATABASE    \tMySQL/MariaDB database to restore\n"
MSG_USAGE+=" -f\tMYDUMPFILE    \tDumpfile pathname\n"
MSG_USAGE+=" -h\tMYHOST        \tHostname or IP to connect (localhost)\n"
MSG_USAGE+=" -p\tMYPORT        \tTCP port to connect (3306)\n"
MSG_USAGE+=" -P\tMYPASSWORD    \tMySQL/MariaDB password\n"
MSG_USAGE+=" -U\tMYUSER        \tMySQL/MariaDB username ($MYUSER)\n"

# Getting options.
#
while getopts ":-:a:A:c:C:d:D:f:h:H:p:P:u:U:" option
do
    case ${option} in
        "-" )
            if   [ "$OPTARG" = "help" ]; then echo -e "$MSG_USAGE" >&2; exit
            else echo "$MSG_BADOPT --$OPTARG" >&2; exit 1
            fi
        ;;
        "A" ) MYDBAUSER="$OPTARG" ;;
        "a" ) MYDBAPASSWORD="$OPTARG" ;;
        "c" ) MYCHARSET="$OPTARG" ;;
        "C" ) MYCONTAINER="$OPTARG" ;;
        "d" | "D" ) MYDATABASE="$OPTARG" ;;
        "f" ) MYDUMPFILE="$OPTARG" ;;
        "h" | "H" ) MYHOST="$OPTARG" ;;
        "P" ) MYPASSWORD="$OPTARG" ;;
        "p" ) MYPORT="$OPTARG" ;;
        "u" | "U" ) MYUSER="$OPTARG" ;;
        \? ) echo "$MSG_BADOPT -$OPTARG" >&2; exit 1
	;;
    esac
done; shift $((OPTIND -1))
#
# All options have been processed.

# Checks the dependencies.
#
# Conditional dependencies (according to native or dockerized environment).
if [ -n "$MYCONTAINER" ]; then
    # Dockerized
    additem="docker"
else
    # Native - MySQL or MariaDB CLI?
    if [ -n "$(which mysql)" ]
    then additem="mysql"
    else additem="mariadb"; fi
fi
# Common dependencies.
TR=$(which tr 2>/dev/null)
if [ -z "$TR" ]; then echo "$MSG_MISSINGDEP tr."; exit 1 ; fi
for item in basename cat cut date dirname file grep gunzip head id locale \
            readlink printf sed sort tail tee wc $additem
do
    if [ -n "$(which $item)" ]
    then export $(echo $item | "$TR" '[:lower:]' '[:upper:]')=$(which $item)
    else echo "$MSG_MISSINGDEP $item." >&2; exit 1; fi
done
#
# Unifies the call of the clients in a native environment.
if [ -z "$MYCONTAINER" ]; then
    if [ -z "$MYSQL" -a -n "$MARIADB" ]; then MYSQL="$MARIADB"; fi
fi
#
# All dependencies are available via "$THECOMMAND" (upper case) call.

# Sanitizing the parameters.
# Most of them are only arbitrary restrictions (reliable source: TODO!)
#
[[ -n "$MYDBAUSER" ]] && [[ ! "$MYDBAUSER" =~ ^([[:alnum:]]|[.-_\\+])*$ ]] \
&& echo -e "$MSG_BADPARAM $MYDBAUSER\n$MSG_USAGE" >&2 && exit 1
#
[[ -n "$MYDBAPASSWORD" ]] && [[ ! "$MYDBAPASSWORD" =~ ^([[:alnum:]]|[ !~&#$<>()%+-_.])*$ ]] \
&& echo -e "$MSG_BADPARAM $MYDBAPASSWORD\n$MSG_PERCENT\n$MSG_USAGE" >&2 && exit 1
#
[[ -n "$MYCONTAINER" ]] && [[ ! "$MYCONTAINER" =~ ^([[:alnum:]]|[-_])*$ ]] \
&& echo -e "$MSG_BADPARAM $MYCONTAINER\n$MSG_USAGE" >&2 && exit 1
#
[[ -n "$MYDATABASE" ]] && [[ ! "$MYDATABASE" =~ ^([[:alnum:]]|[_])*$ ]] \
&& echo -e "$MSG_BADPARAM $MYDATABASE\n$MSG_USAGE" >&2 && exit 1
#
[[ -n "$MYDUMPFILE" ]] && [[ ! "$MYDUMPFILE" =~ ^([[:alnum:]]|[ .-_/])*$ ]] \
&& echo -e "$MSG_BADPARAM $MYDUMPFILE\n$MSG_USAGE" >&2 && exit 1
# https://tools.ietf.org/html/rfc1123#page-13 (relaxed)
[[ -z "$MYHOST" ]] && MYHOST="localhost"
[[ -n "$MYHOST" ]] && [[ ! "$MYHOST" =~ ^([[:alnum:]]|[.-])*$ ]] \
&& echo -e "$MSG_BADPARAM $MYHOST\n$MSG_USAGE" >&2 && exit 1
# https://tools.ietf.org/html/rfc6056 (relaxed)
[[ -z "$MYPORT" ]] && MYPORT=3306
[[ -n "$MYPORT" ]] && [[ ! "$MYPORT" =~ ^[1-9]([[:digit:]]){0,4}$ ]] \
&& echo -e "$MSG_BADPARAM $MYPORT\n$MSG_USAGE" >&2 && exit 1
#
[[ -n "$MYPASSWORD" ]] && [[ ! "$MYPASSWORD" =~ ^([[:alnum:]]|[ !~&#$<>()%+-_.])*$ ]] \
&& echo -e "$MSG_BADPARAM $MYPASSWORD\n$MSG_PERCENT\n$MSG_USAGE" >&2 && exit 1
#
[[ -n "$MYUSER" ]] && [[ ! "$MYUSER" =~ ^([[:alnum:]]|[.-_\\+])*$ ]] \
&& echo -e "$MSG_BADPARAM $MYUSER\n$MSG_USAGE" >&2 && exit 1
#
# We've at least a minimally checked parameters.

# Need to be root or a Docker manager user if the DB runs in a container.
#
[[ -n "$MYCONTAINER" ]] && [[ "$USER" != 'root' ]] \
&& [[ -z "$(echo "$("$ID" -Gn "$USER") " | "$GREP" ' docker ')" ]] \
&& echo "$MSG_DOCKERGRPNEED" >&2 && exit 1 #"

# If the RDBMS is dockerized the container must be running.
#
[[ -n "$MYCONTAINER" ]] \
&& [[ -z "$("$DOCKER" ps -q -f name=$MYCONTAINER)" ]] \
&& echo "$MSG_DOESNOTRUN $MYCONTAINER" >&2 && exit 1

# Determines the database to restore.
#
# Lack of -d the 1st non-option parameter is the database's name.
if [ -z "$MYDATABASE" -a -n "$1" ]; then MYDATABASE="$1"; shift; fi
# The database's name is mandatory.
if [ -z "$MYDATABASE" ]
then echo -e "$MSG_USAGE" >&2; exit 1; fi
# A humble sanitization.
if [[ ! "$MYDATABASE" =~ ^([[:alnum:]]|[_])*$ ]]
then echo -e "$MSG_USAGE" >&2; exit 1; fi
# Silently refuses the MySQL/MariaDB internal databases.
for veto in $vetodatabases ""
do
    [[ "$MYDATABASE" = "$veto" ]] && exit 0
done
#
# We've a database name to restore.

# Determines the dumpfile.
#
# Lack of -f the 2nd non-option parameter is the database's name.
if [ -z "$MYDUMPFILE" -a -n "$1" ]; then MYDUMPFILE="$1"; shift; fi
# The dumpfile is mandatory.
if [ -z "$MYDUMPFILE" ]
then echo -e "$MSG_USAGE" >&2; exit 1; fi
# The MYDUMPFILE must point to a readable file.
# If it is an existing symlink dereferences it to ensure, it points to a file.
if [ -h "$MYDUMPFILE" ]; then
    if [[ "$("$READLINK" "$MYDUMPFILE")" != /* ]]
    # relative path in symlink
    then MYDUMPFILE="$("$DIRNAME" "$MYDUMPFILE")/$("$READLINK" "$MYDUMPFILE")"
    # absolute path in symlink
    else MYDUMPFILE="$("$READLINK" "$MYDUMPFILE")"; fi
fi
# Let's check it!
if [ ! -r "$MYDUMPFILE" -o  ! -f "$MYDUMPFILE" ]
then echo -e "$MSG_BADDUMP $MYDUMPFILE"; exit 1; fi
#
# We've an existing dumpfile.

# Tries to get the locale settings (actually CHARACTER SET) of this dump.
#
if [ -z "$MYCHARSET" ]; then
    # Let's identify the file is gzipped or not.
    UNPACKER=$("$FILE" --mime-type "$MYDUMPFILE")
    UNPACKER=${UNPACKER##* }	# The last word is the MIME-type.
    # We'll use gunzip or cat (a dummy unzipper), according to the MIME type.
    [[ "$UNPACKER" = 'application/gzip' ]] \
    && UNPACKER="$GUNZIP" \
    || UNPACKER="$CAT"
    # Collects all character set adjustments from the dumpfile.
    MYCHARSET=$("$CAT" "$MYDUMPFILE" | "$UNPACKER" | "$GREP" -B2 -i 'CREATE TABLE' | \
                "$GREP" -i 'character_set_client =' | "$SORT" -u)
    # Trims the character set's name itself (the first word after the equal sign).
    [[ -n "$MYCHARSET" ]] && MYCHARSET=$(echo -e "$MYCHARSET" | "$SED" 's/^.*= \(.*\) .*$/\1/') #'
fi
#
# We've a raw guess about the character sets used.

# Finds the LOGFILE to use.
#
# If the folder containing the MYDUMPFILE is writable, we will use a
# logfile with the same name as the dumpfile but with .log extension.
[[ -w "$("$DIRNAME" "$MYDUMPFILE")" ]] \
&& LOGFILE="${MYDUMPFILE%.gz}" \
&& LOGFILE="${LOGFILE%.*}.log" \
|| LOGFILE="/dev/null"
#
# We've a suitable logfile.

# Opens the log and takes care to close it when finish.
#
echo "$(eval $LOGSTAMP) Starting job #$$ $("$TR" '\0' ' ' < /proc/$$/cmdline)" | \
     "$TEE" -a "$LOGFILE"
# Sets a trap to make always a corresponding exit log entry as well.
function close_log() {
    echo -e "$(eval $LOGSTAMP) Finished job #$$ $("$TR" '\0' ' ' < /proc/$$/cmdline)\n" | \
	 "$TEE" -a "$LOGFILE"
}
trap -- 'close_log' EXIT
#
# We started logging.

# Prepopulates two SQL command skeletons (macros).
#
# This skeleton makes the SQL calls independent to the environment
# (native or dockerized) and credentials. We need only actualize the
# CONNECT, DATABASE and SQLVERB clauses then eval $DO_SQLSTREAM or
# $DO_SQLVERB. Warning: the parameters must be sanitized!
#
if [ -n "$MYCONTAINER" ]; then
    # When MySQL runs in the container.
    #
    if [ -n "$("$DOCKER" exec $MYCONTAINER which mysql)" ]; then
	DO_SQLVERB="\"\$DOCKER\" exec -e MYSQL_PWD=\"\$MYSQL_PWD\" \$MYCONTAINER mysql "
    # When MariaDB runs in the container.
    elif [ -n "$("$DOCKER" exec $MYCONTAINER which mariadb)" ]; then
	DO_SQLVERB="\"\$DOCKER\" exec -e MYSQL_PWD=\"\$MYSQL_PWD\" \$MYCONTAINER mariadb "
	DO_SQLSTREAM="\"\$DOCKER\" exec -i -e MYSQL_PWD=\"\$MYSQL_PWD\" \$MYCONTAINER /bin/bash -c \"mariadb "
    # Otherwise gives it up here.
    else
	echo -e "$MSG_BADDBTYPE in $MYCONTAINER." | "$TEE" -a "$LOGFILE" >&2
	echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2
	exit 1
    fi
    # Common parameters.
    DO_SQLVERB+="\$CONNECT -sN --ssl-verify-server-cert=false \$DATABASE "
    DO_SQLVERB+="-e \"\$SQLVERB\""
    DO_SQLSTREAM+="\$CONNECT -sN --ssl-verify-server-cert=false \$DATABASE \""
else
    # Native environment.
    #
    DO_SQLVERB="export \"MYSQL_PWD\"; \"\$MYSQL\" "
    DO_SQLVERB+="\$CONNECT -sN --ssl-verify-server-cert=false \$DATABASE "
    DO_SQLVERB+="-e \"\$SQLVERB\""
    SO_SQLSTREAM="$DO_SQLVERB"
fi
#
# We've two suitable SQL macros.

# Are we able to connect to the database, preferably as a DBA?
#
SQLVERB="SELECT 1;"
result=""
# Sets the default DBA username for dockerized and native RDBMS as well.
if [ -z "$MYDBAUSER" ]; then
    [[ -n "$MYCONTAINER" ]] \
    && MYDBAUSER="root" \
    || MYDBAUSER="root"
fi
# In a native environment we'll try the local connection
# (Unix-domain socket) first.
if [ -z "$MYCONTAINER" ]; then
    CONNECT=""
    DATABASE=""
    result=$(eval "$DO_SQLVERB" 2>/dev/null); excode=$?
    result="${result//[[:space:]]/}"
fi
if [ "$result" != "1" ]; then
    #
    # On failure we'll try the TCP connection.
    MYSQL_PWD="$MYDBAPASSWORD"
    CONNECT="-u $MYDBAUSER -h $MYHOST -P $MYPORT"
    result=$(eval "$DO_SQLVERB" 2>/dev/null); excode=$?
    result="${result//[[:space:]]/}"
    if [ "$result" != "1" ]; then
	#
	# On failure we'll try the TCP connection with non-DBA credentials.
	MYSQL_PWD="$MYPASSWORD"
	CONNECT="-u $MYUSER -h $MYHOST -P $MYPORT"
	result=$(eval "$DO_SQLVERB" 2>/dev/null); excode=$?
	result="${result//[[:space:]]/}"
	if [ "$result" != "1" ]; then
	    #
	    # On failure we'll give up here.
	    [[ "$result" != "1" ]] \
	    && echo -e "$MSG_FAILCONN" | "$TEE" -a "$LOGFILE" >&2 \
	    && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
	    && exit 1
	fi
    fi
fi
#
# We've valid MYSQL_PWD and CONNECT clauses.

# Checks the superuser privilege.
# Better check: TODO!
#
ISDBA=false
DATABASE=""
SQLVERB="SHOW GRANTS;"
result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
result="${result//[[:space:]]/}"
if [[ $excode -eq 0 && \
     "$result" =~ ^GRANTALLPRIVILEGESON\*\.\*.*WITHGRANTOPTION$ ]]; then
    ISDBA=true
else
    echo -e "$MSG_NONSUPER" | "$TEE" -a "$LOGFILE" >&2
    echo -e "$MSG_NONBLOCKING" | "$TEE" -a "$LOGFILE" >&2
fi
#
# We know we're a DB superuser or not.

# Following steps need the superuser privileges.
# Lack of this we'll skip them.
#
if $ISDBA; then
    DATABASE="mysql"

    # Checks the minimal MySQL/MariaDB version.
    #
    SQLVERB="SELECT version();"
    result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
    result="${result//[[:space:]]/}"
    [[ $excode -ne 0 ]] \
    && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
    && exit 1
    [[ -z "$result" ]] \
    && echo -e "$MSG_FAILVER" | "$TEE" -a "$LOGFILE" >&2 \
    && echo -e "$MSG_BLOCKING"  | "$TEE" -a "$LOGFILE" >&2 \
    && exit 1
    # Let's extract a comparable RDBMS version from the result.
    dbversion=${result%%-*}		# strips anyone after 1st dash (including)
    dbversion=(${dbversion//./ })	# converts to an array
    dbversion=$("$PRINTF" '10#%03d%03d%03d' ${dbversion[@]}) # 3 times 3 digits 0-padded decimal
    if [ -n "$(echo "$result" | "$GREP" -i "mariadb")" ]; then
	# MariaDB version check.
        (( dbversion < MARIADBMIN )) \
	&& echo -e "$MSG_OLDRDBMS: $result" | "$TEE" -a "$LOGFILE" >&2 \
        && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
	&& exit 1
    else
	# MySQL version check.
	(( dbversion < MYSQLMIN )) \
	&& echo -e "$MSG_OLDRDBMS: $result" | "$TEE" -a "$LOGFILE" >&2 \
	&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
	&& exit 1
    fi
    #
    # RDBMS version is proper.

    # Database user revision.
    #
    # If '$MYUSER'@'$MYHOST' exists, it will provide the necessaty privileges.
    # If '$MYUSER'@'%' doesn't exist, it will create it then provide the
    # necessary privileges.
    #
    # Checks '$MYUSER'@'$MYHOST'
    SQLVERB="SELECT COUNT(1) FROM mysql.user WHERE user = '$MYUSER' AND host = '$MYHOST'; "
    result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
    result="${result//[[:space:]]/}"
    if [[ $excode -eq 0 && $result -eq 1 ]]; then
	# It exists, let's give it privileges.
	echo -e "GRANT" | "$TEE" -a "$LOGFILE"
	SQLVERB="GRANT ALL PRIVILEGES ON $MYDATABASE.* TO '$MYUSER'@'$MYHOST'; "
	result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
	result="${result//[[:space:]]/}"
	[[ $excode -ne 0 ]] \
	&& echo -e "$MSG_FAILGRANT $MYUSER@$MYHOST" | "$TEE" -a "$LOGFILE" >&2 \
	&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
	&& exit 1
    fi
    # Checks '$MYUSER'@'%' as well.
    SQLVERB="SELECT COUNT(1) FROM mysql.user WHERE user = '$MYUSER' AND host = '%'; "
    result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
    result="${result//[[:space:]]/}"
    if [[ $excode -eq 0 && $result -ne 1 ]]; then
	# Creates if it doesn't exist yet.
	echo -e "CREATE USER %" | "$TEE" -a "$LOGFILE"
	SQLVERB="CREATE USER '$MYUSER'@'%' IDENTIFIED BY '$MYPASSWORD'; "
	result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
	result="${result//[[:space:]]/}"
	# Gives it up here if something went wrong.
	[[ $excode -ne 0 ]] \
	&& echo -e "$MSG_FAILUSER $MYUSER@%" | "$TEE" -a "$LOGFILE" >&2 \
	&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
	&& exit 1
    fi
    # Let's give it privileges.
    echo -e "GRANT %" | "$TEE" -a "$LOGFILE"
    SQLVERB="GRANT ALL PRIVILEGES ON $MYDATABASE.* TO '$MYUSER'@'%'; "
    result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
    result="${result//[[:space:]]/}"
    [[ $excode -ne 0 ]] \
    && echo -e "$MSG_FAILGRANT $MYUSER@%" | "$TEE" -a "$LOGFILE" >&2 \
    && echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
    && exit 1
    #
    # We've the database user(s) with the proper grants.

    # Drops all existing connections to the database being restored.
    #
    echo -e "KILL CONNECTIONS" | "$TEE" -a "$LOGFILE"
    # List of the active connections.
    SQLVERB="SELECT id FROM information_schema.processlist "
    SQLVERB+="WHERE db = '$MYDATABASE';"
    result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
    if [[ $excode -ne 0 ]]; then
	echo -e "$MSG_FAILTOKILL" | "$TEE" -a "$LOGFILE" >&2
	echo -e "$MSG_NONBLOCKING" | "$TEE" -a "$LOGFILE" >&2
    else
	# Enumerates and tries to kill these connections.
	for connection in $result ""
	do
	    if [ -n "$connection" ]; then
		SQLVERB="KILL $connection;"
		eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2); excode=$?
		[[ $excode -ne 0 ]] \
		&& echo -e "$MSG_FAILKILL $connection" | "$TEE" -a "$LOGFILE" >&2 \
		&& echo -e "$MSG_NONBLOCKING" | "$TEE" -a "$LOGFILE" >&2
	    fi
	done
    fi
    #
    # Connections have eliminated (we hope).
fi
#
# Done with the superuser part.

# Drops the database.
#
echo -e "DROP DATABASE" | "$TEE" -a "$LOGFILE"
DATABASE=""
SQLVERB="DROP DATABASE IF EXISTS $MYDATABASE;"
result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
result="${result//[[:space:]]/}"
[[ $excode -ne 0 ]] \
&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
&& exit 1
#
# Recreates the database.
#
echo -e "CREATE DATABASE" | "$TEE" -a "$LOGFILE"
DATABASE=""
SQLVERB="CREATE DATABASE $MYDATABASE;"
result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
result="${result//[[:space:]]/}"
[[ $excode -ne 0 ]] \
&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
&& exit 1
#
# We've an empty database.

# Sets the default character set.
#
if [ -n "$MYCHARSET" ]; then
    echo -e "ALTER CHARACTER SET" | "$TEE" -a "$LOGFILE"
    # If it is ambigous, we'll ignore it.
    if [ "$(echo -e "$MYCHARSET" | "$WC" -l)" -ne 1 ]; then
	echo -e "$MSG_AMBIGOUS" | "$TEE" -a "$LOGFILE" >&2
	echo -e "$MSG_NONBLOCKING" | "$TEE" -a "$LOGFILE" >&2
	MYCHARSET=""
    else
	# Let's set it.
	DATABASE="$MYDATABASE"
	SQLVERB="ALTER DATABASE $MYDATABASE CHARACTER SET $MYCHARSET;"
	result=$(eval "$DO_SQLVERB" 2> >("$TEE" -a "$LOGFILE" >&2)); excode=$?
	result="${result//[[:space:]]/}"
	[[ $excode -ne 0 ]] \
	&& echo -e "$MSG_BLOCKING" | "$TEE" -a "$LOGFILE" >&2 \
	&& exit 1
    fi
fi
#
# We've the character set adjusted.

# Restores the database from the dump.
#
# This isn't so straightforward as in e.g PostgreSQL.
# We'll use the database user's credentials, not the superuser's
# to mitigate the effect of an unsanitized dump.
#
echo -e "RESTORE" | "$TEE" -a "$LOGFILE"
# Let's identify the file is gzipped or not.
UNPACKER=$("$FILE" --mime-type "$MYDUMPFILE")
UNPACKER=${UNPACKER##* }	# The last word is the MIME-type.
# We'll use gunzip or cat (a dummy unzipper), according to the MIME type.
[[ "$UNPACKER" = 'application/gzip' ]] \
&& UNPACKER="$GUNZIP" \
|| UNPACKER="$CAT"
# This is a sed expression to modify the security definers within the dump.
MOD_DEFINER="s/DEFINER=.*@[^ ]*/DEFINER=CURRENT_USER/"
#
# We'll read the dump, on the fly unpack it and modify the security definer,
# then we'll pass the data stream to the MySQL client.
#
DATABASE="$MYDATABASE"
SQLVERB=""
(eval "$DO_SQLSTREAM") \
      < <("$CAT" "$MYDUMPFILE" | "$UNPACKER" | "$SED" "$MOD_DEFINER") \
      >/dev/null 2> >("$TEE" -a "$LOGFILE" >&2); excode=$?
# Unfortunately the result code doesn't differentiate the
# blocking and non-blocking states.
[[ $excode -ne 0 ]] \
&& echo -e "$MSG_NONZERO: $excode" | "$TEE" -a "$LOGFILE" >&2
#
# We had a try to restore the database - the result isn't properly defined.

# Closing log entry will be handled via EXIT trap.
#
# That's all, Folks! :)
