#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/lib/ulib/auto" || exit 1
urequire install

function display_help() {
    uecho "$scriptname: gérer les mises à jour sur une base de données

Les mises à jours sont dans un répertoire UPDATEDIR, qui contient un répertoire
pour chaque base de données à gérer. Ces répertoires sont des DATABASEDIR. Les
fichiers sql peuvent être placés dans le répertoire UPDATEDIR pour les mises à
jour administratives, ou dans DATABASEDIR pour les mises à jour de la base de
données.

Les fichiers sql peuvent contenir dans les premières lignes du fichier un tag
@sqlmig sous forme de commentaire. On peut avoir:
-- @sqlmig drop
    identifie une suite de requêtes à exécuter pour supprimer la base de données
    et recommencer à zéro
-- @sqlmig create
    identifie une suite de requêtes à exécuter pour créer la base de données.
-- @sqlmig admin
    identifie une mise à jour administrative. Les fichiers situés dans UPADTEDIR
    sont automatiquement considérés comme des mises à jour administratives même
    en l'absence de ce tag.

Les mises à jour administratives sont exécutées avec les paramètres du fichier
my.cnf (resp. ora.conf) et sont typiquement utilisées pour créer les comptes
utilisateurs pour la connexion à la base de données et leur attribuer des droits
d'accès. Ces mises à jour sont exécutées sans sélectionner une base de données
en particulier.

Les mises à jour utilisateurs sont exécutées avec les paramètres du fichier
my.cnf (resp. ora.conf) augmenté du fichier my-DBNAME.cnf (res. ora-DBNAME.conf)
Elles sont exécutées avec la base de données concernée sélectionnée.

Il y a deux modes d'exécution: prod ou devel. En mode prod, la suppression de
base de données est interdite. De plus, tous les fichiers de mise à jour de la
forme *.devel.sql sont ignorés. Cela permet de fignoler une mise à jour sans
risquer de l'exécuter en production.

Les fichiers de la forme maint-*.sql sont ignorés sauf s'ils sont explicitement
sélectionnés avec l'option -f ce qui permet de suivre les opérations de
maintenance faites sur la base de données.

USAGE
    $scriptname [options]

OPTIONS
    -g, --admin-defaults-file ADMINDEFAULTS
        Spécifier un fichier de configuration à utiliser pour se connecter à la
        base de données pour les opérations de maintenance comme la création ou
        la suppression de la base de données.
        * Dans le mode MySQL, ce fichier est chargé en plus du fichier par
          défaut my.cnf
        * Dans le mode Oracle, ce fichier est chargé en plus du fichier par
          défaut ora.conf.
    -C, --defaults-file USERDEFAULTS
        Spécifier un fichier de configuration à utiliser pour se connecter à la
        base de données pour les opérations de mise à jour.
        * Dans le mode MySQL, ce fichier est chargé en plus du fichier par
          défaut my-DATABASE.cnf
        * Dans le mode Oracle, ce fichier est chargé en plus du fichier par
          défaut ora-DATABASE.conf.
    -u, --user USER
    -p, --password PASSWORD
    -h, --host HOST
    --port PORT
    --socket SOCKET
    --character-set CHARSET
    -s, --oracle-sid ORACLE_SID
    --nls-lang NLS_LANG
        Spécifier les valeurs pour la connexion à la base de données. Les
        options --host, --port et --socket ne sont valides que pour MySQL. Ces
        valeurs remplacent les valeurs par défaut chargées depuis my.cnf
        Les options --sid et --nls-lang ne sont valides que pour Oracle. Ces
        valeurs remplacent les valeurs par défaut chargées depuis ora.conf
    --suffix SUFFIX
        Lors de la connexion à la base de données, toujours ajouter le suffixe
        spécifié au nom de la base de données. Cette valeur peut être spécifiée
        dans la section [sqlmig] du fichier my.cnf ou avec la variable SUFFIX
        pour ora.conf
    --csv2sql
        Convertir les fichiers CSV en SQL. C'est la valeur par défaut. Ce
        paramètre peut être spécifié dans my.cnf sous la forme
            [sqlmig]
            csv2sql=1
        ou dans ora.conf
            CSV2SQL=1
        Pour le moment, cette conversion n'est supportée que pour MySQL. Tous
        les fichiers de la forme [NUM]TABLE[-data].csv sont transformés en une
        suite d'insertion dans la table TABLE. Une variante insère les données
        dans la table après l'avoir vidée avec 'truncate table'. Les fichiers
        doivent être de la forme [NUM]TABLE-data_truncate.csv
    --csv-null VALUE
        Lors de la conversion des fichiers .csv en .sql, considérer que VALUE
        représente la valeur NULL. Par défaut, utiliser la chaine vide
    --csv-null-empty
    --csv-null-mysql
    --csv-null-upper
        Aliases pour --csv-null '', --csv-null '\\N' et --csv-null NULL respectivement
    --no-csv2sql
        Ne pas convertir les fichiers CSV en SQL. Ce paramètre peut être
        spécifié dans my.cnf sous la forme
            [sqlmig]
            csv2sql=0
        ou dans ora.conf
            CSV2SQL=0
    --load-data DEFAULT|LOCAL|SERVER
        Si l'option --no-csv2sql est activée, spécifier le type de chargement
        effectué pour les fichiers CSV.
        * Avec LOCAL (la valeur par défaut), la directive 'load data local' est
          utilisée. Cependant les erreurs éventuelles sont ignorées parce que le
          serveur n'a pas de moyen pour arrêter la transmission des informations
          par le client.
        * Avec SERVER, la directive 'load data' est utilisée. Les erreurs
          éventuelles sont affichées et arrêtent l'importation des données. Par
          contre, le fichier doit être accessible par le serveur MySQL puisque
          les données sont chargées directement par le serveur.
          NB: avec MariaDB sur systemd, l'accès à /home, /root et /run/user est
          désactivé par défaut. Pour pouvoir utiliser ce mode avec des fichiers
          de l'utilisateur, il faut lancer ces commandes:
              [ \$(id -u) == 0 ] && sudo= || sudo=sudo
              \$sudo mkdir -p /etc/systemd/system/mariadb.service.d
              \$sudo tee /etc/systemd/system/mariadb.service.d/dontprotecthome.conf <<<\$'[Service]\nProtectHome=false'
              \$sudo systemctl daemon-reload
        * Avec DEFAULT, utiliser SERVER si on communique avec le serveur MySQL
          local (c'est à dire si host n'est pas spécifié ou vaut 127.*, ::1 ou
          localhost). Sinon, utiliser LOCAL
    --profile PROFILE
    -P, --prod
    -T, --test
        Activer le profil spécifié. Les options -P et -T sont des aliases pour
        les options --profile prod et --profile test respectivement.
        Quand un profil est activé, les fichiers de configuration par défaut
        deviennent respectivement \$PROFILE-my.cnf et \$PROFILE-ora.conf au lieu
        de my.cnf et ora.conf. De plus, ces fichiers sont aussi recherchés dans
        le répertoire /etc/sqlmig, en fonction du mode: en mode production, on
        cherche d'abord dans /etc/sqlmig. En mode développement, on cherche
        d'abord dans le répertoire local.

    --mysql
    --oracle
        Spécifier le type de base de données à gérer. Par défaut, on gère une
        base de type mysql.
        Avec le type oracle, il faut adapter la lecture de cette documentation:
        à chaque fois que l'on parle de base de données, il s'agit en réalité de
        gérer un utilisateur. La connexion à la base de données proprement dite
        est configurée dans le fichier ora.conf
    -0, --init
        Créer les fichiers initiaux pour gérer une base de données. Cette option
        est utilisée pour le développement
    -e, --export DESTUPDATEDIR
        Exporter les définitions de bases de données et mises à jour du
        répertoire courant vers le répertoire DESTUPDATEDIR
    -c, --connect
        Se connecter avec le client natif (mysql ou sqlplus) sur la base de
        données courante.
    -r, --restore-test
        Restaurer une sauvegarde d'une base de production sur la test. Cette
        option n'est (actuellement) supportée que pour MySQL.
        L'argument attendu est un fichier de sauvegarde DATABASE.sql[.gz]
        effectué avec la commande
            mysqldump --databases --add-drop-database DATABASE
        Dans le fichier spécifié, on remplace toutes les occurences de DATABASE
        par DATABASE_test, ensuite on lance (re)création de la base de données
            mysql <DATABASE_test.sql
    --update-all
        Mettre à jour la base de données. C'est l'option par défaut
    -t, --updatedir UPDATEDIR
        Spécifier le répertoire qui contient les répertoires de mises à jour
        pour chaque base de données.
    -d, --databasedir DATABASEDIR
        Spécifier le répertoire qui contient les mises à jour à appliquer pour
        une base de données spécifique. Le nom de la base de données à gérer est
        déterminé à partir du nom du répertoire. Si cette option n'est pas
        spécifiée, tous les répertoires de base de données de UPDATEDIR sont
        considérés.
    -b, --database DATABASE
        Spécifier le nom de la base de données. En principe le nom de la base de
        données est calculé à partir du nom du répertoire DATABASEDIR. Cette
        option peut être utilisée par exemple pour créer une base de test à
        partir des définitions d'une base de prod.
        Notez que le suffixe spécifié avec l'option --suffix est toujours
        rajouté au nom de la base de données.
    -f, --update-one UPDATES...
        Forcer l'application des mises à jour spécifiées. Ne pas mettre à jour
        l'état des mises à jour installées.
        Important: Avec cette option, tout se passe comme si les seuls fichiers
        existant sont ceux spécifiés. Par exemple, quel que soit l'ordre dans
        lequel les fichiers sont spécifiés, ils sont évalués dans l'ordre
        alphanumérique et sont ignorés s'ils n'ont pas l'extension .sql

    -n, --fake
        Ne pas faire les mises à jour, afficher simplement ce qui serait fait
    --force, --continue-on-error
        Ne pas s'arrêter en cas d'erreur de mise à jour
    --no-data-csv
        Ne pas procéder à la conversion des fichiers CSV en SQL
    --force-data-csv
        Forcer la conversion des fichiers CSV en SQL. Par défaut, la conversion
        n'est faite que si le fichier csv est plus récent que le fichier sql
        correspondant.
    --devel-mode
        Activer le mode développement. Ce mode est automatiquement activé si
        l'utilisateur courant n'est pas root. La suppression des bases de
        données n'est autorisée qu'en mode développement
    -Z, --recreate
        Supprimer la base de données puis la recréer et appliquer les mises à
        jour.
    --drop-only
        Supprimer la base de données uniquement. Ne pas la recréer.
    --create-only
        Créer la base de données uniquement. Ne pas appliquer les mises à jour"
}

function mconf_get() {
    local cnf="$1" section="$2" name="$3"
    awkrun <"$cnf" section="$section" name="$name" '
    BEGIN { in_section = 0 }
    !in_section && $0 == "[" section "]" { in_section = 1; next }
    in_section && $0 == "[" section "]" { in_section = 0; next }
    in_section && $0 ~ "^" name " *=" {
        gsub(/^[^=]*= */, "")
        print
        exit
    }
    '
}

function __check_devel_dir() {
    # $1 = P (le préfixe)
    # parentdir et cwd doivent être définis. initialiser le cas échéant la
    # variable updatedir
    local updatedir dbdir
    if [ "${parentdir%/$1}" != "$parentdir" ]; then
        # On est dans un répertoire de la forme P/$1/DB, autosélectioner P/$1
        upvar updatedir "$parentdir"
        upvar dbdir "$cwd"
        return 0
    elif [ "${cwd%/$1}" != "$cwd" ]; then
        # On est dans un répertoire de la forme P/$1, autosélectioner P/$1
        upvar updatedir "$cwd"
        return 0
    elif [ -d "$cwd/$1" ]; then
        # On est dans un répertoire P tel que P/$1 existe, autosélectioner P/$1
        upvar updatedir "$cwd/$1"
        return 0
    fi
    return 1
}
function __check_mysql_prod_dir() {
    # parentdir et cwd doivent être définis
    local dir updatedir dbdir
    if [ "${parentdir%/updates}" != "$parentdir" ]; then
        # Si on est dans un répertoire de la forme P/updates/DB, alors
        # sélectioner P/updates si P contient le marqueur .mysqld-update
        dir="$parentdir"
        dbdir="$cwd"
    elif [ -d "$cwd/updates" ]; then
        # Si on est dans un répertoire P tel que P/updates existe, alors
        # sélectioner P/updates si P contient le marqueur .mysqld-update
        dir="$cwd/updates"
    fi
    if [ -n "$dir" -a -f "$(dirname -- "$dir")/.mysqld-update" ]; then
        upvar updatedir "$dir"
        [ -n "$dbdir" ] && upvar dbdir "$dbdir"
        return 0
    fi
    return 1
}

function find_mysqldupdatedir() {
    # en commençant à partir du répertoire $1 qui vaut par défaut le répertoire
    # courant, chercher un répertoire contenant le fichier témoin .mysqld-update
    local dir="$1" origdir
    [ -n "$dir" ] || dir="$(pwd)"
    setx dir=abspath "$dir"
    origdir="$dir"

    while true; do
        if [ -f "$dir/.mysqld-update" ]; then
            echo "$dir"
            return 0
        fi
        if [ -z "$dir" -o "$dir" == / -o "$dir" == "$HOME" ]; then
            echo "$origdir"
            return 1
        fi
        setx dir=dirname -- "$dir"
    done
}
function check_mysqldupdatedir() {
    local exportdir="$1"
    [ -f "$exportdir/.mysqld-update" ] || die "$(ppath "$exportdir"): n'est pas un répertoire mysqld-update"
}

function have_tag() {
    # tester si le fichier $2 a le tag "@sqlmig $1"
    <"$2" awk '{print; if ($0 == "") exit}' | quietgrep '^--  *@sqlmig  *'"$1"' *$'
}

function abort_on_error() {
    [ -z "$force" ] && die "$@"
}

function ensure_dbtype() {
    local dir="$1" type="$2"
    if [ "$type" == auto ]; then
        if [ -f "$dir/my.cnf" ]; then
            dbtype=mysql
        elif [ -f "$dir/ora.conf" ]; then
            dbtype=oracle
        else
            die "Vous devez spécifier le type --mysql ou --oracle"
        fi
    else
        dbtype="$type"
    fi
}
function ensure_dbmode() {
    local dbtype="$1" mode="$2"
    if [ "$mode" == auto ]; then
        local profile_mode
        if [ -n "$profile" ]; then
            profile_mode="${profile}_PROFILE_MODE"
            profile_mode="${!profile_mode}"
        fi
        if [ -n "$profile_mode" ]; then
            dbmode="$profile_mode"
        elif [ "$dbtype" == mysql ]; then
            is_root && dbmode=prod || dbmode=devel
        else
            dbmode=prod
        fi
    else
        dbmode="$mode"
    fi
}

function set_dbdirs() {
    if [ -n "$dbdir" ]; then
        dbdirs=("$dbdir")
    elif [ -z "$updatedir" ]; then
        array_lsfiles files . "*.sql"
        if [ ${#files[*]} -gt 0 ]; then
            enote "Autosélection répertoire courant"
            dbdir="$cwd"
            dbdirs=("$dbdir")
        else
            die "Vous devez spécifier l'option -b"
        fi
    elif [ -n "$dbname" ]; then
        dbdirs=("$updatedir/$dbname")
    else
        array_lsdirs dbdirs "$updatedir"
    fi

    if [ -n "$dbname" -a ${#dbdirs[*]} -gt 1 ]; then
        die "Avec l'option -n, une seule base de données doit être spécifiée"
    fi
}

function fix_csv2sql() {
    if [ -z "$csv2sql" ]; then
        # valeur par défaut
        csv2sql=1
    elif is_yes "$csv2sql"; then
        csv2sql=1
    elif is_no "$csv2sql"; then
        csv2sql=0
    fi
}
function fix_csv_null() {
    # si csv_null a une valeur vide, c'est déjà la valeur par défaut
    case "$csv_null" in
    empty) csv_null=;;
    mysql) csv_null='\N';;
    upper) csv_null=NULL;;
    esac
}
function fix_load_data() {
    if [ -z "$load_data" ]; then
        # valeur par défaut
        load_data=local
    else
        case "${load_data,,}" in
        default|d*) load_data=default;;
        local|l*) load_data=local;;
        server|s*) load_data=server;;
        esac
    fi
    if [ "$load_data" == default ]; then
        case "$host" in
        ""|localhost|127.*|::1) load_data=server;;
        *) load_data=local;;
        esac
    fi
}

function get_csvinfo() {
    # afficher les informations sur un fichier csv: nom de la table, et s'il
    # faut faire un truncate
    local csvname="$(basename -- "$1")"
    local script='{
  truncate = ($0 ~ /-data_truncate(.devel)?.csv$/)? "1": ""
  sub(/^.*\//, "")
  sub(/^(([A-Z][-.A-Z0-9]*[0-9]-?)|([0-9][-.0-9]*-?))/, "")
  sub(/\.csv$/, "")
  sub(/\.devel$/, "")
  sub(/-data(_[a-z]+*)?$/, "")
  print "truncate=" truncate
  gsub(/'\''/, "'\'\\\\\'\''")
  print "table='\''" $0 "'\''"
}'
    awk "$script" <<<"$csvname"
}

################################################################################
# MySQL

function get_mysql_admindb() { echo "sqlmig_admin_db_"; }
function get_mysql_admintb() { echo "sqlmig_updates_"; }
function get_mysql_usertb() { echo "${1}_updates_"; }

function mysql_ve() {
    #local r; set -x #DEBUG
    mysql "${@:3}" ${2:+-D "$2"} ${1:+-e "$1"}
    #r=$?; set +x; return $r #DEBUG
}
function mysql_user_ve() {
    mysql_ve "$1" "$dbname" "${userargs[@]}" "${mysqlargs[@]}" "${@:2}"
}
function mysql_user_qe() {
    if show_debug; then mysql_user_ve "$@"
    elif show_verbose; then mysql_user_ve "$@" >/dev/null
    else mysql_user_ve "$@" >&/dev/null
    fi
}
function mysql_admin_ve() {
    mysql_ve "$1" "$2" "${adminargs[@]}" "${mysqlargs[@]}" "${@:3}"
}
function mysql_admin_qe() {
    if show_debug; then mysql_admin_ve "$@"
    elif show_verbose; then mysql_admin_ve "$@" >/dev/null
    else mysql_admin_ve "$@" >&/dev/null
    fi
}
function mysql_tbconf() {
    [ -n "$fake" ] && return

    # s'assurer que la table des mises à jour existe
    local dbname="$1" tb="$2"

    if [ -z "$dbname" ]; then
        # admin
        setx dbname=get_mysql_admindb
        if ! mysql_admin_qe "select 1" "$dbname"; then
            mysql_admin_qe "create database $dbname"
        fi
        [ -n "$tb" ] || setx tb=get_mysql_admintb "$dbname"
        if ! mysql_admin_qe "select count(*) from $tb" "$dbname"; then
            mysql_admin_qe "create table $tb (
 name varchar(128) not null primary key
,tem_done int(1)
,date_start datetime
,date_done datetime
)" "$dbname" || die "create table $tb"
        fi
    else
        # user
        [ -n "$tb" ] || setx tb=get_mysql_usertb "$dbname"
        if ! mysql_user_qe "select count(*) from $tb"; then
            mysql_user_qe "create table $tb (
 name varchar(128) not null primary key
,tem_done int(1)
,date_start datetime
,date_done datetime
)" || die "create table $tb"
        fi
    fi
}
function mysql_get_done() {
    local name="$1" dbname="$2" tb="$3"
    if [ -z "$dbname" ]; then
        # admin
        setx dbname=get_mysql_admindb
        [ -n "$tb" ] || setx tb=get_mysql_admintb "$dbname"
        mysql_admin_ve "select name from $tb where name = '$name' and tem_done = 1" "$dbname" -N
    else
        # user
        [ -n "$tb" ] || setx tb=get_mysql_usertb "$dbname"
        mysql_user_ve "select name from $tb where name = '$name' and tem_done = 1" -N
    fi
}
function mysql_before_update() {
    local dbname="$1" tb="$2"
    if [ -z "$dbname" ]; then
        # admin
        setx dbname=get_mysql_admindb
        [ -n "$tb" ] || setx tb=get_mysql_admintb "$dbname"
        mysql_admin_ve "insert into $tb (name, tem_done, date_start) values ('$name', 0, sysdate()) on duplicate key update tem_done = 0, date_start = sysdate(), date_done = null" "$dbname"
    else
        # user
        [ -n "$tb" ] || setx tb=get_mysql_usertb "$dbname"
        mysql_user_ve "insert into $tb (name, tem_done, date_start) values ('$name', 0, sysdate()) on duplicate key update tem_done = 0, date_start = sysdate(), date_done = null"
    fi
}
function mysql_after_update() {
    local dbname="$1" tb="$2"
    if [ -z "$dbname" ]; then
        # admin
        setx dbname=get_mysql_admindb
        [ -n "$tb" ] || setx tb=get_mysql_admintb "$dbname"
        mysql_admin_ve "update $tb set tem_done = 1, date_done = sysdate() where name = '$name'" "$dbname"
    else
        # user
        [ -n "$tb" ] || setx tb=get_mysql_usertb "$dbname"
        mysql_user_ve "update $tb set tem_done = 1, date_done = sysdate() where name = '$name'"
    fi
}

function mysql_get_defaults() {
    local dir="$1"
    if [ -n "$profile" ]; then
        if [ "$dbmode" == devel ]; then
            if [ -f "$dir/${profile}-my.cnf" ]; then
                echo "$dir/${profile}-my.cnf"; return
            elif [ -f "$PROFILEDIR/${profile}-my.cnf" ]; then
                echo "$PROFILEDIR/${profile}-my.cnf"; return
            fi
        else
            if [ -f "$PROFILEDIR/${profile}-my.cnf" ]; then
                echo "$PROFILEDIR/${profile}-my.cnf"; return
            elif [ -f "$dir/${profile}-my.cnf" ]; then
                echo "$dir/${profile}-my.cnf"; return
            fi
        fi
    fi
    echo "$dir/my.cnf"
}
function mysql_set_adminargs() {
    local dir="$1" defaults
    adminargs=()
    setx defaults=mysql_get_defaults "$dir"
    if [ -f "$defaults" ]; then
        array_add adminargs --defaults-file="$defaults"
        if [ -n "$admindefaults" ]; then
            array_add adminargs --defaults-extra-file="$admindefaults"
        fi
    elif [ -n "$admindefaults" ]; then
        array_add adminargs --defaults-file="$admindefaults"
    fi
    [ ${#adminargs[*]} -gt 0 ] || array_add adminargs --default-character-set utf8
}
function mysql__mconf_get() {
    local defaults="$1" tmp
    if [ -n "$set_suffix" ]; then
        setx tmp=mconf_get "$defaults" sqlmig suffix
        [ -n "$tmp" ] && suffix="$tmp"
    fi
    if [ -n "$set_csv2sql" ]; then
        setx tmp=mconf_get "$defaults" sqlmig csv2sql
        [ -n "$tmp" ] && {
            csv2sql="$tmp"
            fix_csv2sql
        }
    fi
    if [ -n "$set_csv_null" ]; then
        setx tmp=mconf_get "$defaults" sqlmig csv_null
        [ -n "$tmp" ] && {
            csv_null="$tmp"
            fix_csv_null
        }
    fi
    if [ -n "$set_load_data" ]; then
        if [ -z "$host" ]; then
            # load_data==default requière host
            setx host=mconf_get "$defaults" client host
        fi
        setx tmp=mconf_get "$defaults" sqlmig load-data
        [ -n "$tmp" ] && {
            load_data="$tmp"
            fix_load_data
        }
    fi
}
function mysql_set_userargs() {
    local dir="$1" dbname="$2" defaults
    local set_suffix set_csv2sql set_csv_null set_load_data
    userargs=()
    setx defaults=mysql_get_defaults "$dir"
    [ -z "$suffix" ] && set_suffix=1
    [ -z "$csv2sql" ] && set_csv2sql=1
    [ -z "$csv_null" ] && set_csv_null=1
    [ -z "$load_data" ] && set_load_data=1
    if [ -f "$defaults" ]; then
        array_add userargs --defaults-file="$defaults"
        mysql__mconf_get "$defaults"
        if [ -n "$userdefaults" ]; then
            array_add userargs --defaults-extra-file="$userdefaults"
            mysql__mconf_get "$userdefaults"
        elif [ -f "$dir/my-${dbname}.cnf" ]; then
            array_add userargs --defaults-extra-file="$dir/my-${dbname}.cnf"
            mysql__mconf_get "$dir/my-${dbname}.cnf"
        fi
    elif [ -n "$userdefaults" ]; then
        array_add userargs --defaults-file="$userdefaults"
        mysql__mconf_get "$userdefaults"
    elif [ -f "$dir/my-${dbname}.cnf" ]; then
        array_add userargs --defaults-file="$dir/my-${dbname}.cnf"
        mysql__mconf_get "$dir/my-${dbname}.cnf"
    fi
    [ ${#userargs[*]} -gt 0 ] || array_add userargs --default-character-set utf8
    # initialiser les valeurs par défaut
    fix_csv2sql
    fix_csv_null
    fix_load_data
}
function mysql_set_mysqlargs() {
    mysqlargs=()
    [ -n "$user" ] && array_add mysqlargs -u "$user"
    [ -n "$pwset" ] && array_add mysqlargs -p"$password"
    [ -n "$host" ] && array_add mysqlargs -h "$host"
    [ -n "$port" ] && array_add mysqlargs -P "$port"
    [ -n "$socket" ] && array_add mysqlargs -S "$socket"
    [ -n "$charset" ] && array_add mysqlargs --default-character-set "$charset"
    [ -n "$force" ] && array_add mysqlargs -f
    array_add mysqlargs -B
}
MYSQL_ADMIN_CONF_DONE=
function mysql_admin_update() {
    local name="$1" update="$2" updateone="$3" done
    if [ -z "$updateone" ]; then
        if [ -z "$MYSQL_ADMIN_CONF_DONE" ]; then
            MYSQL_ADMIN_CONF_DONE=1
            mysql_tbconf
        fi
        if ! setx done=mysql_get_done "$name"; then
            [ -n "$fake" ] || die
        fi
        [ -n "$done" ] && return
    fi

    estep "$name"
    [ -n "$fake" ] && return

    if [ -z "$updateone" ]; then
        mysql_before_update || die
    fi
    cat "$update" | mysql_admin_ve || abort_on_error
    if [ -z "$updateone" ]; then
        mysql_after_update || die
    fi
}
function mysql_user_update() {
    local name="$1" update="$2" dbname="$3" updateone="$4" done
    if [ -z "$updateone" ]; then
        if ! setx done=mysql_get_done "$name" "$dbname"; then
            [ -n "$fake" ] || die
        fi
        [ -n "$done" ] && return
    fi

    estep "$name"
    [ -n "$fake" ] && return

    if [ -z "$updateone" ]; then
        mysql_before_update "$dbname" || die
    fi
    if [[ "$update" == *.sql ]]; then
        # SQL
        cat "$update" | mysql_user_ve || abort_on_error
    else
        # CSV
        local truncate table local sql
        eval "$(get_csvinfo "$update")"
        [ -n "$truncate" ] && sql="truncate table $table;"
        [ "$load_data" == local ] && local=1 || local=
        sql="${sql}load data${local:+ local} infile '$update' into table \`$table\`
 character set 'utf8'
 fields terminated by ','
 optionally enclosed by '\\\"'
 escaped by '\\\\'
 lines terminated by '\\n'
 starting by ''
 ignore 1 lines
 ($(<"$update" awk '{ print; exit }'));"
        echo "$sql" | mysql_user_ve || abort_on_error
    fi
    if [ -z "$updateone" ]; then
        mysql_after_update "$dbname" || die
    fi
}

################################################################################
# Oracle

function get_oracle_admindb() { echo "sqlmig_admin_db_"; }
function get_oracle_admintb() { echo "sqlmig_updates_"; }
function get_oracle_usertb() { echo "${1}_updates_"; }

function oracle_sqlplus() {
    # lancer sqlplus sans affichage superflu, et spooler vers $SQLMIGLOG
    #local r; set -x #DEBUG
    echo "\
set pagesize 0
set feedback off
set linesize 8192
set tab off
whenever oserror exit failure
whenever sqlerror exit sql.sqlcode
spool '$SQLMIGLOG' append" >"$OPDIR/login.sql"
    local connect="$1@$ORACLE_SID" sysdba="$2"; shift; shift
    ORACLE_PATH="$OPDIR" sqlplus -S "$connect" ${sysdba:+as sysdba} "$@"
    #r=$?; set +x; return $r #DEBUG
}
function qe() {
    # lancer la commande $@: si elle retourne un code d'erreur, afficher le
    # résultat de la commande sur stderr si on est en mode verbeux, sinon ne
    # rien afficher
    local r output
    output="$("$@")"; r=$?
    if [ ${#output} -gt 0 ]; then
        show_verbose && echo "$output" 1>&2
    fi
    return $r
}

function oracle_admin_query() {
    # lancer sqlplus avec la connexion admin
    oracle_sqlplus "$ADMINCONNECT" "$ADMINDBA" "$@"
}
function oracle_admin_ve() {
    # lancer une requête admin, ne pas masquer le résultat
    if [ $# -gt 0 ]; then
        show_debug && edebug "query: $*"
        oracle_admin_query <<<"$*"
    else
        oracle_admin_query
    fi
}
function oracle_admin_qe() {
    # lancer une requête admin en masquant le résultat
    if [ $# -gt 0 ]; then
        show_debug && edebug "query: $*"
        qe oracle_admin_query <<<"$*"
    else
        qe oracle_admin_query
    fi
}
function oracle_admin_ne() {
    # lancer une requête admin et retourner vrai si elle affiche un résultat
    if [ $# -gt 0 ]; then
        show_debug && edebug "query: $*"
        [ -n "$(oracle_admin_query <<<"$*")" ]
    else
        [ -n "$(oracle_admin_query)" ]
    fi
}
function oracle_admin_have_user() {
    # tester en mode admin si le user $1 existe
    oracle_admin_ne "select username from all_users where username = '${1^^}';"
}
function oracle_admin_have_table() {
    # tester en mode admin si le table $1 existe (avec éventuellement le owner $2)
    local sql="select table_name from all_tables where table_name = '${1^^}'"
    [ -n "$2" ] && sql="$sql and owner = '${2^^}'"
    oracle_admin_ne "$sql;"
}

function oracle_user_query() {
    # lancer sqlplus avec la connexion user
    oracle_sqlplus "$USERCONNECT" "$USERDBA" "$@"
}
function oracle_user_ve() {
    # lancer une requête user, ne pas masquer le résultat
    if [ $# -gt 0 ]; then
        show_debug && edebug "query: $*"
        oracle_user_query <<<"$*"
    else
        oracle_user_query
    fi
}
function oracle_user_qe() {
    # lancer une requête user en masquant le résultat
    if [ $# -gt 0 ]; then
        show_debug && edebug "query: $*"
        qe oracle_user_query <<<"$*"
    else
        qe oracle_user_query
    fi
}
function oracle_user_ne() {
    # lancer une requête user et retourner vrai si elle affiche un résultat
    if [ $# -gt 0 ]; then
        show_debug && edebug "query: $*"
        [ -n "$(oracle_user_query <<<"$*")" ]
    else
        [ -n "$(oracle_user_query)" ]
    fi
}
function oracle_user_have_user() {
    # tester en mode user si le user $1 existe
    oracle_user_ne "select username from all_users where username = '${1^^}';"
}
function oracle_user_have_table() {
    # tester en mode user si le table $1 existe (avec éventuellement le owner $2)
    local sql="select table_name from all_tables where table_name = '${1^^}'"
    [ -n "$2" ] && sql="$sql and owner = '${2^^}'"
    oracle_user_ne "$sql;"
}

function oracle_tbconf() {
    [ -n "$fake" ] && return

    # s'assurer que la table des mises à jour existe
    local dbname="$1" tb="$2"
    if [ -z "$dbname" ]; then
        # admin
        setx dbname=get_oracle_admindb
        #if ! oracle_admin_have_user "$dbname"; then
        #    oracle_admin_ve "create user $dbname; grant connect to $dbname;"
        #fi
        [ -n "$tb" ] || setx tb=get_oracle_admintb "$dbname"
        local owner="${ADMINCONNECT%%/*}"
        [ -n "owner" ] || owner=system
        if ! oracle_admin_have_table "$tb" "$owner"; then
            oracle_admin_ve "create table $tb (
 name varchar(128) not null primary key
,tem_done number(1)
,date_start timestamp
,date_done timestamp
);" || die "create table $tb"
        fi
    else
        # user
        [ -n "$tb" ] || setx tb=get_oracle_usertb "$dbname"
        local owner="${USERCONNECT%%/*}"
        [ -n "owner" ] || owner=system
        if ! oracle_user_have_table "$tb" "$owner"; then
            oracle_user_ve "create table $tb (
 name varchar(128) not null primary key
,tem_done number(1)
,date_start timestamp
,date_done timestamp
);" || die "create table $tb"
        fi
    fi
}
function oracle_get_done() {
    # afficher le nom d'une mise à jour si elle a été appliquée
    local name="$1" dbname="$2" tb="$3"
    if [ -z "$dbname" ]; then
        # admin
        setx dbname=get_oracle_admindb
        [ -n "$tb" ] || setx tb=get_oracle_admintb "$dbname"
        oracle_admin_ve "select name from $tb where name = '$name' and tem_done = 1;"
    else
        # user
        [ -n "$tb" ] || setx tb=get_oracle_usertb "$dbname"
        oracle_user_ve "select name from $tb where name = '$name' and tem_done = 1;"
    fi
}
function oracle_before_update() {
    # préparer l'exécution d'une mise à jour
    local dbname="$1" tb="$2"
    if [ -z "$dbname" ]; then
        # admin
        setx dbname=get_oracle_admindb
        [ -n "$tb" ] || setx tb=get_oracle_admintb "$dbname"
        oracle_admin_ve "\
merge into $tb d
using (select '$name' name from dual) s
on (d.name = s.name)
when matched then update set d.tem_done = 0, d.date_start = sysdate, d.date_done = null
when not matched then insert (name, tem_done, date_start) values ('$name', 0, sysdate);
commit;"
    else
        # user
        [ -n "$tb" ] || setx tb=get_oracle_usertb "$dbname"
        oracle_user_ve "\
merge into $tb d
using (select '$name' name from dual) s
on (d.name = s.name)
when matched then update set d.tem_done = 0, d.date_start = sysdate, d.date_done = null
when not matched then insert (name, tem_done, date_start) values ('$name', 0, sysdate);
commit;"
    fi
}
function oracle_after_update() {
    # valider l'exécution d'une mise à jour
    local dbname="$1" tb="$2"
    if [ -z "$dbname" ]; then
        # admin
        setx dbname=get_oracle_admindb
        [ -n "$tb" ] || setx tb=get_oracle_admintb "$dbname"
        oracle_admin_ve "\
update $tb set tem_done = 1, date_done = sysdate where name = '$name';
commit;"
    else
        # user
        [ -n "$tb" ] || setx tb=get_oracle_usertb "$dbname"
        oracle_user_ve "\
update $tb set tem_done = 1, date_done = sysdate where name = '$name';
commit;"
    fi
}

function oracle_ensure_opdir() {
    if [ -z "$OPDIR" ]; then
        ac_set_tmpdir OPDIR
        >"$SQLMIGLOG"
    fi
}
function oracle_get_defaults() {
    local dir="$1"
    if [ -n "$profile" ]; then
        if [ "$dbmode" == devel ]; then
            if [ -f "$dir/${profile}-ora.conf" ]; then
                echo "$dir/${profile}-ora.conf"; return
            elif [ -f "$PROFILEDIR/${profile}-ora.conf" ]; then
                echo "$PROFILEDIR/${profile}-ora.conf"; return
            fi
        else
            if [ -f "$PROFILEDIR/${profile}-ora.conf" ]; then
                echo "$PROFILEDIR/${profile}-ora.conf"; return
            elif [ -f "$dir/${profile}-ora.conf" ]; then
                echo "$dir/${profile}-ora.conf"; return
            fi
        fi
    fi
    echo "$dir/ora.conf"
}
function oracle_source_adminconf() {
    local dir="$1"
    unset ORACLE_SID NLS_LANG ADMINCONNECT USERCONNECT SQLMIGLOG
    setx defaults=oracle_get_defaults "$dir"
    [ -f "$defaults" ] && source "$defaults"
    [ -n "$admindefaults" ] && {
        source "$admindefaults" || die
    }
    [ -n "$oracle_sid" ] && ORACLE_SID="$oracle_sid"
    [ -n "$nls_lang" ] && NLS_LANG="$nls_lang"
    export ORACLE_SID NLS_LANG
    if [ -n "$ADMINCONNECT" ]; then
        ADMINDBA=
    else
        ADMINCONNECT=/
        ADMINDBA=1
    fi
    [ -n "$SQLMIGLOG" ] || SQLMIGLOG="/tmp/sqlmig-${ORACLE_SID}-${dbname}.log"
}
function oracle_source_userconf() {
    local dir="$1" dbname="$2"
    unset ORACLE_SID NLS_LANG ADMINCONNECT USERCONNECT SQLMIGLOG SUFFIX CSV2SQL CSV_NULL
    setx defaults=oracle_get_defaults "$dir"
    [ -f "$defaults" ] && source "$defaults"
    [ -f "$dir/ora-${dbname}.conf" ] && source "$dir/ora-${dbname}.conf"
    [ -n "$userdefaults" ] && {
        source "$userdefaults" || die
    }
    [ -n "$oracle_sid" ] && ORACLE_SID="$oracle_sid"
    [ -n "$nls_lang" ] && NLS_LANG="$nls_lang"
    export ORACLE_SID NLS_LANG
    if [ -n "$ADMINCONNECT" ]; then
        ADMINDBA=
    else
        ADMINCONNECT=/
        ADMINDBA=1
    fi
    if [ -n "$USERCONNECT" ]; then
        USERDBA=
    else
        USERCONNECT=/
        USERDBA=1
    fi
    [ -n "$SQLMIGLOG" ] || SQLMIGLOG="/tmp/sqlmig-${ORACLE_SID}-${dbname}.log"
    [ -z "$suffix" ] && suffix="$SUFFIX"
    [ -z "$csv2sql" ] && csv2sql="$CSV2SQL"
    [ -z "$csv_null" ] && csv_null="$CSV_NULL"
    fix_csv2sql
    fix_csv_null
}
ORACLE_ADMIN_CONF_DONE=
function oracle_admin_update() {
    local name="$1" update="$2" updateone="$3" done
    if [ -z "$updateone" ]; then
        if [ -z "$ORACLE_ADMIN_CONF_DONE" ]; then
            ORACLE_ADMIN_CONF_DONE=
            oracle_tbconf
        fi
        setx done=oracle_get_done "$name" || die
        [ -n "$done" ] && return
    fi

    estep "$name"
    [ -n "$fake" ] && return

    if [ -z "$updateone" ]; then
        oracle_before_update || die
    fi
    cat "$update" | oracle_admin_ve || abort_on_error
    if [ -z "$updateone" ]; then
        oracle_after_update || die
    fi
}
function oracle_user_update() {
    local name="$1" update="$2" dbname="$3" updateone="$4" done
    if [ -z "$updateone" ]; then
        setx done=oracle_get_done "$name" "$dbname" || die
        [ -n "$done" ] && return
    fi

    estep "$name"
    [ -n "$fake" ] && return

    if [ -z "$updateone" ]; then
        oracle_before_update "$dbname" || die
    fi
    cat "$update" | oracle_user_ve || abort_on_error
    if [ -z "$updateone" ]; then
        oracle_after_update "$dbname" || die
    fi
}

################################################################################

MODE=auto
PROFILEDIR=/etc/sqlmig
prod_PROFILE_MODE=prod
test_PROFILE_MODE=devel
set_defaults sqlmig

admindefaults=
userdefaults=
user=
password=
pwset=
host=
port=
socket=
charset=
oracle_sid=
nls_lang=
suffix=
csv2sql=
csv_null=
load_data=
profile=
type=auto
action=update
updatedir=
exportdir=
dbdir=
dbname=
updateone=
force=
data_csv=auto
mode="$MODE"
drop=
drop_only=
create_only=
args=(
    --help '$exit_with display_help'
    -g:,--admin-defaults-file: admindefaults=
    -C:,--defaults-file: userdefaults=
    -u:,--user: user=
    -p:,--password: '$set@ password; pwset=1'
    -h:,--host: host=
    --port: port=
    --socket: socket=
    --character-set: charset=
    -s:,--oracle-sid: oracle_sid=
    --nls-lang: nls_lang=
    --suffix: suffix=
    --csv2sql csv2sql=1
    --csv-null: csv_null=
    --csv-null-empty csv_null=empty
    --csv-null-mysql csv_null='\N'
    --csv-null-upper csv_null=NULL
    --no-csv2sql csv2sql=0
    --load-data: load_data=
    --profile: profile=
    -P,--prod profile=prod
    -T,--test profile=test
    --mysql type=mysql
    --oracle type=oracle
    -0,--init action=init
    -e:,--export: '$action=export; set@ exportdir'
    -c,--connect action=connect
    -r,--restore-test action=restore_test
    --update-all action=update
    -t:,--updatedir: updatedir=
    -d:,--databasedir: dbdir=
    -b:,--database: dbname=
    -f,--update-one updateone=1
    -n,--fake fake=1
    --force,--continue-on-error force=1
    --no-data-csv data_csv=
    --force-data-csv data_csv=force
    --prod-mode-dangerous mode=prod
    --devel-mode mode=devel
    -Z,--recreate drop=1
    --drop-only '$drop=1; drop_only=1'
    --create-only create_only=1
)
parse_args "$@"; set -- "${args[@]}"

setx cwd=pwd
if [ -z "$dbdir" -a -z "$updatedir" ]; then
    setx parentdir=dirname -- "$cwd"
    if __check_devel_dir src/main/resources/database; then
        enote "Autosélection src/main/resources/database/"
    elif __check_devel_dir config/sqlmig; then
        enote "Autosélection config/sqlmig/"
    elif __check_devel_dir support/database; then
        enote "Autosélection support/database/"
    elif __check_devel_dir database; then
        enote "Autosélection database/"
    elif __check_mysql_prod_dir; then
        [ "$type" == auto ] && type=mysql
    fi
fi

[ -n "$updatedir" ] && setx updatedir=abspath "$updatedir"
[ -n "$dbdir" ] && setx dbdir=abspath "$dbdir"

################################################################################
if [ "$action" == init ]; then
    [ -n "$dbdir" -a -z "$dbname" ] && setx dbname=basename "$dbdir"
    [ -n "$dbname" ] || dbname="$1"
    read_value ${dbname:+-i} "Entrez le nom de la base de données" dbname "$dbname"

    if [ -z "$dbdir" -a -n "$updatedir" ]; then
        dbdir="$updatedir/$dbname"
    elif [ -z "$dbdir" ]; then
        dbdir="$dbname"
    fi
    read_value ${dbdir:+-i} "Entrez le répertoire dans lequel créer les définitions" dbdir "$dbdir"
    setx dbdir=abspath "$dbdir"

    ask_yesno "Voulez-vous créer les fichiers initiaux pour la base de données $dbname dans le répertoire $(ppath "$dbdir")?" O || die

    estep "Création du répertoire $dbdir"
    [ -d "$dbdir" ] || mkdir -p "$dbdir" || die

    [ "$type" == auto ] && type=mysql

    if [ "$type" == mysql ]; then
        if [ ! -f "$dbdir/00dropdb.sql" ]; then
            estep "00dropdb.sql"
            echo >"$dbdir/00dropdb.sql" "\
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
-- @sqlmig drop

drop database if exists @@database@@;"
        fi

        if [ ! -f "$dbdir/01createdb.sql" ]; then
            estep "01createdb.sql"
            echo >"$dbdir/01createdb.sql" "\
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
-- @sqlmig create

create database @@database@@;"
        fi

        if [ ! -f "$dbdir/02grants.sql" ]; then
            estep "02grants.sql"
            echo >"$dbdir/02grants.sql" "\
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
-- @sqlmig admin

create user '$dbname' identified by '$dbname';
grant all privileges on $dbname.* to '$dbname';"
        fi

        if [ ! -f "$dbdir/my.cnf" ]; then
            estep "my.cnf"
            echo >"$dbdir/my.cnf" "\
# Paramètres de connexion par défaut
[client]
#user=
#password=
#host=localhost
#port=3306
#socket=/var/run/mysqld/mysqld.sock

[mysql]
default-character-set=utf8

[sqlmig]
#suffix="
        fi

        if [ ! -f "$dbdir/my-${dbname}.cnf" ]; then
            estep "my-${dbname}.cnf"
            echo >"$dbdir/my-${dbname}.cnf" "\
# Paramètres de connexion pour $dbname
[client]
#user=
#password=

[sqlmig]
#csv2sql=0
csv_null=
#load-data=default"
       fi

    elif [ "$type" == oracle ]; then
        if [ ! -f "$dbdir/00dropuser.sql" ]; then
            estep "00dropuser.sql"
            echo >"$dbdir/00dropuser.sql" "\
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
-- @sqlmig drop

drop user $dbname cascade;"
        fi

        if [ ! -f "$dbdir/01createuser.sql" ]; then
            estep "01createuser.sql"
            echo >"$dbdir/01createuser.sql" "\
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
-- @sqlmig create

create user $dbname identified by \"$dbname\";
grant connect to $dbname;
grant resource to $dbname;"
        fi

        if [ ! -f "$dbdir/ora.conf" ]; then
            estep "ora.conf"
            echo >"$dbdir/ora.conf" "\
# Paramètres de connexion par défaut
ORACLE_SID=orcl
NLS_LANG=FRENCH_FRANCE.AL32UTF8
#NLS_LANG=AMERICAN_AMERICA.AL32UTF8
# paramètres de connexion pour les mises à jour administratives
# si aucune valeur n'est spécifiée, la valeur effective est '/ as sysdba' mais
# cela requière que la mise à jour soit faite avec une connexion locale.
ADMINCONNECT=
# paramètres de connexion pour les mises à jour utilisateur
USERCONNECT=$dbname/password
# logs des mises à jour
SQLMIGLOG=\"/tmp/sqlmig-\${ORACLE_SID}-${dbname}.log\"
# divers
#SUFFIX=
#CSV2SQL=0
CSV_NULL="
        fi

    else
        die "BUG: $type: type non implémenté"
    fi

    exit 0

################################################################################
elif [ "$action" == export ]; then
    [ -n "$exportdir" ] || setx exportdir=find_mysqldupdatedir
    check_mysqldupdatedir "$exportdir"
    setx exportdir=abspath "$exportdir/updates"

    if [ -n "$dbdir" ]; then
        dbdirs=("$dbdir")
    elif [ -z "$updatedir" ]; then
        array_lsfiles files . "*.sql" "*my.cnf" "my-*.cnf" "*ora.conf" "ora-*.conf"
        if [ ${#files[*]} -gt 0 ]; then
            enote "Autosélection répertoire courant"
            dbdir="$cwd"
            dbdirs=("$dbdir")
        else
            die "Vous devez spécifier l'option -b"
        fi
    elif [ -n "$dbname" ]; then
        dbdirs=("$updatedir/$dbname")
    else
        array_lsdirs dbdirs "$updatedir"
    fi

    for dbdir in "${dbdirs[@]}"; do
        setx dbname=basename -- "$dbdir"
        # TEMPLATE.d est spécial dans mysqld-update: il faut l'ignorer
        [ "$dbname" != TEMPLATE.d ] || continue

        etitled "$dbname"
        destdir="$exportdir/$dbname"
        array_lsfiles updates "$dbdir"
        for update in "${updates[@]}"; do
            [ -d "$destdir" ] || mkdir -p "$destdir"
            [[ "$update" == "*.devel.sql" ]] && continue
            copy_update "$update" "$destdir"
        done
        eend; eclearp
    done

    exit 0

################################################################################
elif [ "$action" == connect ]; then
    set_dbdirs

    force_dbname="$dbname"
    force_suffix="$suffix"
    for dbdir in "${dbdirs[@]}"; do
        dbname="$force_dbname"
        [ -n "$dbname" ] || setx dbname=basename "$dbdir"
        suffix="$force_suffix"
        etitle "$dbname"

        ensure_dbtype "$dbdir" "$type"
        ensure_dbmode "$dbtype" "$mode"

        if [ "$dbtype" == mysql ]; then
            # construire les paramètres pour mysql
            mysql_set_userargs "$dbdir" "$dbname"
            mysql_set_mysqlargs

            if [ -n "$suffix" ]; then
                estepi "Suffixe: $dbname --> $dbname$suffix"
                dbname="$dbname$suffix"
            fi

            array_del mysqlargs -B # désactiver le mode batch
            mysql "${userargs[@]}" "${mysqlargs[@]}" -D "$dbname"

        elif [ "$dbtype" == oracle ]; then
            # lire les paramètres
            oracle_source_userconf "$dbdir" "$dbname"
            oracle_ensure_opdir

            if [ -n "$suffix" ]; then
                estepi "Suffixe: $dbname --> $dbname$suffix"
                dbname="$dbname$suffix"
            fi

            sqlplus "$USERCONNECT@$ORACLE_SID" ${USERDBA:+as sysdba} "$@"

        else
            die "BUG: $dbtype: type non implémenté"
        fi

        eend
    done
    exit 0

################################################################################
elif [ "$action" == restore_test ]; then
    set_dbdirs

    if [ ${#dbdirs[*]} -gt 1 ]; then
        die "Avec --restore-test, une seule base de données doit être spécifiée"
    fi

    dbdir="${dbdirs[0]}"
    [ -n "$dbname" ] || setx dbname=basename "$dbdir"

    etitle "$dbname"

    ensure_dbtype "$dbdir" "$type"
    ensure_dbmode "$dbtype" "$mode"

    [ "$dbtype" == mysql ] || die "Seule les bases de type MySQL sont supportées"

    # construire les paramètres pour mysql
    mysql_set_userargs "$dbdir" "$dbname"
    mysql_set_mysqlargs

    if [ -n "$suffix" ]; then
        estepi "Suffixe: $dbname --> $dbname$suffix"
        dbname="$dbname$suffix"
    fi

    dump="$1"
    [ -n "$dump" ] || die "Vous devez spécifier le fichier de dump"
    [ -f "$dump" ] || die "$dump: fichier de dump introuvable"

    ac_set_tmpdir tmpdir
    if [[ "$dump" == *.gz ]]; then
        gzip -dc "$dump" >"$tmpdir/prod.sql"
    elif [[ "$dump" == *.sql ]]; then
        cat "$dump" >"$tmpdir/prod.sql"
    else
        die "$dump: n'est pas un fichier sql"
    fi
    dump="$tmpdir/prod.sql"

    pname="$dbname"
    tname="${dbname}_test"
    sed <"$tmpdir/prod.sql" >"$tmpdir/test.sql" "\
s/\`$pname\`/\`$tname\`/g
s/\`${pname}_updates_\`/\`${tname}_updates_\`/g"

    enote "Vous allez restaurer un fichier de sauvegarde de le base $pname vers la base $tname"
    ask_yesno "Voulez-vous continuer?" X || die

    array_del mysqlargs -B # désactiver le mode batch
    mysql "${userargs[@]}" "${mysqlargs[@]}" <"$tmpdir/test.sql"
    ac_clean "$tmpdir"

    exit 0

################################################################################
elif [ "$action" != update ]; then
    die "BUG: $action: action non implémentée"
fi

################################################################################
# update

set_dbdirs
if [ -n "$updateone" ]; then
    updatefiles=()
    for updatefile in "$@"; do
        array_add updatefiles "$(abspath "$updatefile")"
    done
fi
function should_update() {
    local update="$1" name
    setx name=basename "$update"
    if [ -n "$updateone" ]; then
        # prendre tous les fichiers sélectionnés par -f
        array_contains updatefiles "$update"
        return $?
    elif [ "${name#maint-}" != "$name" ]; then
        # ignorer les opérations de maintenance par défaut
        return 1
    elif [ "$dbmode" != devel -a "${name%.devel.*}" != "$name" ]; then
        # si on est en mode autre que devel, le nom ne doit pas se terminer
        # par .devel.*
        return 1
    fi
    return 0
}

# répertoire temporaire pour Oracle
OPDIR=

if [ -n "$updatedir" ]; then
    ## mises à jour administratives
    array_lsfiles updates "$updatedir" "*.sql"
    if [ ${#updates[*]} -gt 0 ]; then
        ensure_dbtype "$updatedir" "$type"
        ensure_dbmode "$dbtype" "$mode"
        if [ "$dbtype" == mysql ]; then
            # construire les paramètres pour mysql
            mysql_set_adminargs "$updatedir"
            mysql_set_mysqlargs

            # Mises à jour
            etitled "Mises à jour admin"
            for update in "${updates[@]}"; do
                should_update "$update" || continue
                mysql_admin_update "${update#$updatedir/}" "$update" "$updateone"
            done
            eend; eclearp

        elif [ "$dbtype" == oracle ]; then
            # lire les paramètres
            oracle_source_adminconf "$updatedir"
            oracle_ensure_opdir

            # Mises à jour
            etitled "Mises à jour"
            for update in "${updates[@]}"; do
                should_update "$update" || continue
                oracle_admin_update "${update#$updatedir/}" "$update" "$updateone"
            done
            eend; eclearp
        fi
    fi
fi

## mises à jour utilisateur
force_dbname="$dbname"
force_suffix="$suffix"
for dbdir in "${dbdirs[@]}"; do
    dbname="$force_dbname"
    [ -n "$dbname" ] || setx dbname=basename "$dbdir"
    suffix="$force_suffix"
    etitle "$dbname"

    ensure_dbtype "$dbdir" "$type"
    ensure_dbmode "$dbtype" "$mode"

    ############################################################################
    if [ "$dbtype" == mysql ]; then
        # construire les paramètres pour mysql
        mysql_set_adminargs "$dbdir"
        mysql_set_userargs "$dbdir" "$dbname"
        mysql_set_mysqlargs

        if is_yes "$csv2sql" && [ -n "$data_csv" ]; then
            # Conversion csv --> sql
            array_lsfiles csvs "$dbdir" "*.csv"

            etitled "Conversion"
            for csv in "${csvs[@]}"; do
                setx csvname=basename -- "$csv"
                sql="${csv%.csv}.sql"
                if [ "$data_csv" != force ]; then
                    testnewer "$csv" "$sql" || continue
                fi

                estep "$csvname --> ${csvname%.csv}.sql"
                eval "$(get_csvinfo "$csvname")"
                mysqlloadcsv_args=(
                    ${truncate:+-T}
                    -Z "$csv_null"
                    --prefix "-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8"
                    -nIf "$csv" "$table"
                )
                "$scriptdir/mysqlloadcsv" "${mysqlloadcsv_args[@]}" >"$sql"
            done
            eend; eclearp
        fi

        # lister les mises à jour disponibles
        drops=()
        creates=()
        updates=()
        have_csv=
        array_lsfiles files "$dbdir" "*.sql" "*.csv"
        for file in "${files[@]}"; do
            if [[ "$file" == *.sql ]]; then
                if have_tag drop "$file"; then
                    array_add drops "$file"
                elif have_tag create "$file"; then
                    array_add creates "$file"
                else
                    array_add updates "$file"
                fi
            elif [ ! -f "${file%.csv}.sql" ]; then
                # n'ajouter le CSV que si le fichier SQL correspondant n'existe pas
                array_add updates "$file"
                have_csv=1
            fi
        done

        if [ -n "$suffix" ]; then
            estepi "Suffixe: $dbname --> $dbname$suffix"
            dbname="$dbname$suffix"
        fi
        if is_no "$csv2sql" && [ -n "$have_csv" ]; then
            estepi "Chargement des fichiers CSV avec la méthode $load_data"
        fi

        # Suppression
        if [ -n "$drop" ]; then
            [ "$dbmode" == devel ] || die "La suppression de base de données n'est pas autorisée en mode production"
            etitle "Suppression"
            if ! ask_yesno "Etes-vous sûr de vouloir supprimer la base de données $dbname?" X; then
                ewarn "Suppression annulée, les autres opérations ne seront pas effectuées"
                eend; continue
            fi
            for drop in "${drops[@]}"; do
                should_update "$update" || continue
                setx name=basename "$drop"
                estep "$name"
                [ -n "$fake" ] && continue

                sed "s/@@database@@/$dbname/g" "$drop" | mysql_admin_qe || abort_on_error "drop: $name"
            done
            eend
            [ -n "$drop_only" ] && { eend; continue; }
        fi

        # Création
        if ! mysql_admin_qe "select 1" "$dbname"; then
            etitled "Création"
            for create in "${creates[@]}"; do
                should_update "$update" || continue
                setx name=basename "$create"
                estep "$name"
                [ -n "$fake" ] && continue

                sed "s/@@database@@/$dbname/g" "$create" | mysql_admin_qe || abort_on_error "create: $name"
            done
            eend; eclearp
        fi
        [ -n "$create_only" ] && { eend; continue; }

        # Mises à jour
        etitled "Mises à jour"
        mysql_tbconf "$dbname"
        for update in "${updates[@]}"; do
            should_update "$update" || continue
            if [[ "$update" == *.sql ]]; then
                # fichier SQL
                if have_tag admin "$update"; then
                    [ -n "$updatedir" ] && name="${update#$updatedir/}" || name="${update#$dbdir/}"
                    mysql_admin_update "$name" "$update" "$updateone"
                else
                    mysql_user_update "${update#$dbdir/}" "$update" "$dbname" "$updateone"
                fi
            elif is_no "$csv2sql"; then
                # fichier CSV, ne les traiter que si on est en mode --no-csv2sql
                mysql_user_update "${update#$dbdir/}" "$update" "$dbname" "$updateone"
            fi
        done
        eend; eclearp

    ############################################################################
    elif [ "$dbtype" == oracle ]; then
        [ -n "$OPDIR" ] || ac_set_tmpdir OPDIR

        # lire les paramètres
        oracle_source_userconf "$dbdir" "$dbname"
        oracle_ensure_opdir

        # lister les mises à jour disponibles
        drops=()
        creates=()
        updates=()
        have_csv=
        array_lsfiles files "$dbdir" "*.sql" "*.csv"
        for file in "${files[@]}"; do
            if [[ "$file" == *.sql ]]; then
                if have_tag drop "$file"; then
                    array_add drops "$file"
                elif have_tag create "$file"; then
                    array_add creates "$file"
                else
                    array_add updates "$file"
                fi
            elif [ ! -f "${file%.csv}.sql" ]; then
                # n'ajouter le CSV que si le fichier SQL correspondant n'existe pas
                array_add updates "$file"
                have_csv=1
            fi
        done

        if [ -n "$suffix" ]; then
            estepi "Suffixe: $dbname --> $dbname$suffix"
            dbname="$dbname$suffix"
        fi

        # Suppression
        if [ -n "$drop" ]; then
            [ "$dbmode" == devel ] || die "La suppression de user n'est pas autorisée en mode production"
            etitle "Suppression"
            if ! ask_yesno "Etes-vous sûr de vouloir supprimer le user $dbname?" X; then
                ewarn "Suppression annulée, les autres opérations ne seront pas effectuées"
                eend; continue
            fi
            for drop in "${drops[@]}"; do
                should_update "$update" || continue
                setx name=basename "$drop"
                estep "$name"
                [ -n "$fake" ] && continue

                sed "s/@@database@@/$dbname/g" "$drop" | oracle_admin_ve || abort_on_error "drop: $name"
            done
            eend
            [ -n "$drop_only" ] && { eend; continue; }
        fi

        # Création
        if ! oracle_admin_have_user "$dbname"; then
            etitled "Création"
            for create in "${creates[@]}"; do
                should_update "$update" || continue
                setx name=basename "$create"
                estep "$name"
                [ -n "$fake" ] && continue

                sed "s/@@database@@/$dbname/g" "$create" | oracle_admin_ve || abort_on_error "create: $name"
            done
            eend; eclearp
        fi
        [ -n "$create_only" ] && { eend; continue; }

        # Mises à jour
        etitled "Mises à jour"
        oracle_tbconf "$dbname"
        for update in "${updates[@]}"; do
            should_update "$update" || continue
            if [[ "$update" == *.sql ]]; then
                # fichier SQL
                if have_tag admin "$update"; then
                    [ -n "$updatedir" ] && name="${update#$updatedir/}" || name="${update#$dbdir/}"
                    oracle_admin_update "$name" "$update" "$updateone"
                else
                    oracle_user_update "${update#$dbdir/}" "$update" "$dbname" "$updateone"
                fi
            fi
        done
        eend; eclearp

    ############################################################################
    else
        die "BUG: $dbtype: type non implémenté"
    fi

    eend
done