#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/lib/ulib/ulib" || exit 1
urequire DEFAULTS 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.

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
    --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-mysql
    --csv-null-upper
        Aliases pour --csv-null '\\N' et --csv-null NULL respectivement

    --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.
    --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 UPDATE
        Forcer l'application de la mise à jour spécifiée. Ne pas mettre à jour
        l'état des mises à jour installées. Avec cette option, tout se passe
        comme si le seul fichier existant est celui spécifié.

    -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 convertir les fichiers *-data.csv en fichier .sql
        correspondant. Cette conversion n'est supportée que pour MySQL pour le
        moment, et un fichier de la forme NUMTABLE-data.csv où NUM est une valeur
        numérique est transformé en une suite d'insertions dans la table TABLE.
        La variante NUMTABLE-data_truncate.csv ajoute les données dans la table
        après l'avoir vidée avec truncate.
    --force-data-csv
        Forcer la conversion des fichiers *-data.csv. 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_csv_null() {
    case "$csv_null" in
    mysql) csv_null='\N';;
    upper) csv_null=NULL;;
    esac
}

################################################################################
# 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_csv_null" ]; then
        setx tmp=mconf_get "$defaults" sqlmig csv_null
        [ -n "$tmp" ] && {
            csv_null="$tmp"
            fix_csv_null
        }
    fi
}
function mysql_set_userargs() {
    local dir="$1" dbname="$2" defaults
    local set_suffix set_csv_null
    userargs=()
    setx defaults=mysql_get_defaults "$dir"
    [ -z "$suffix" ] && set_suffix=1
    [ -z "$csv_null" ] && set_csv_null=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
}
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
    cat "$update" | mysql_user_ve || abort_on_error
    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 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 "$csv_null" ] && {
        csv_null="$CSV_NULL"
        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=
csv_null=
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=
    --csv-null: csv_null=
    --csv-null-mysql csv_null='\N'
    --csv-null-upper csv_null=NULL
    --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
    --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 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/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=
csv_null="
        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="
       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 \"password\";
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=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=
#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" != 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
    if [ -n "$updateone" ]; then
        array_contains updatefiles "$update"
        return $?
    else
        setx name=basename "$update"
        if [ "$dbmode" != devel -a "${name%.devel.sql}" != "$name" ]; then
            # si on est en mode autre que devel, le nom ne doit pas se terminer
            # par .devel.sql
            return 1
        fi
        return 0
    fi
}

# 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"

    # Conversion csv --> sql
    array_lsfiles csvs "$dbdir" "*.csv"
    if [ "$dbtype" == mysql -a -n "$data_csv" ]; then
        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 "$(awk '{
  truncate = ($0 ~ /-data_truncate(.devel)?.csv$/)? "1": ""
  sub(/^.*\//, "")
  sub(/^[0-9.]*[0-9]-?/, "")
  sub(/\.csv$/, "")
  sub(/\.devel$/, "")
  sub(/-data(_[a-z]+*)?$/, "")
  print "truncate=" truncate
  gsub(/'\''/, "'\'\\\\\'\''")
  print "table='\''" $0 "'\''"
}' <<<"$csvname")" #"
            "$scriptdir/mysqlloadcsv" >"$sql" ${truncate:+-T} -Z "$csv_null" -nIf "$csv" "$table" --prefix "-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8"
        done
        eend; eclearp
    fi

    # lister les mises à jour disponibles
    drops=()
    creates=()
    updates=()
    array_lsfiles files "$dbdir" "*.sql"
    for file in "${files[@]}"; do
        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
    done

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

        if [ -n "$suffix" ]; then
            estepi "Suffixe: $dbname --> $dbname$suffix"
            dbname="$dbname$suffix"
        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 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
        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

        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 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
        done
        eend; eclearp

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

    eend
done