#!/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" updatefile="$3" done if [ -z "$updatefile" ]; 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 "$updatefile" ]; then mysql_before_update || die fi cat "$update" | mysql_admin_ve || abort_on_error if [ -z "$updatefile" ]; then mysql_after_update || die fi } function mysql_user_update() { local name="$1" update="$2" dbname="$3" updatefile="$4" done if [ -z "$updatefile" ]; 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 "$updatefile" ]; then mysql_before_update "$dbname" || die fi cat "$update" | mysql_user_ve || abort_on_error if [ -z "$updatefile" ]; 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" updatefile="$3" done if [ -z "$updatefile" ]; 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 "$updatefile" ]; then oracle_before_update || die fi cat "$update" | oracle_admin_ve || abort_on_error if [ -z "$updatefile" ]; then oracle_after_update || die fi } function oracle_user_update() { local name="$1" update="$2" dbname="$3" updatefile="$4" done if [ -z "$updatefile" ]; then setx done=oracle_get_done "$name" "$dbname" || die [ -n "$done" ] && return fi estep "$name" [ -n "$fake" ] && return if [ -z "$updatefile" ]; then oracle_before_update "$dbname" || die fi cat "$update" | oracle_user_ve || abort_on_error if [ -z "$updatefile" ]; 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= updatefile= 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: updatefile= -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 [ -n "$updatefile" ] && setx updatefile=abspath "$updatefile" # 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 setx name=basename "$update" [ "$dbmode" == devel -o "${name%.devel.sql}" == "$name" ] || continue [ -z "$updatefile" -o "$updatefile" == "$update" ] || continue name="${update#$updatedir/}" mysql_admin_update "$name" "$update" "$updatefile" 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 setx name=basename "$update" [ "$dbmode" == devel -o "${name%.devel.sql}" == "$name" ] || continue [ -z "$updatefile" -o "$updatefile" == "$update" ] || continue name="${update#$updatedir/}" oracle_admin_update "$name" "$update" "$updatefile" 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 setx name=basename "$drop" [ "$dbmode" == devel -o "${name%.devel.sql}" == "$name" ] || continue [ -z "$updatefile" -o "$updatefile" == "$update" ] || continue 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 setx name=basename "$create" [ "$dbmode" == devel -o "${name%.devel.sql}" == "$name" ] || continue [ -z "$updatefile" -o "$updatefile" == "$update" ] || continue 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 setx name=basename "$update" [ "$dbmode" == devel -o "${name%.devel.sql}" == "$name" ] || continue [ -z "$updatefile" -o "$updatefile" == "$update" ] || continue if have_tag admin "$update"; then [ -n "$updatedir" ] && name="${update#$updatedir/}" || name="${update#$dbdir/}" mysql_admin_update "$name" "$update" "$updatefile" else name="${update#$dbdir/}" mysql_user_update "$name" "$update" "$dbname" "$updatefile" 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 setx name=basename "$drop" [ "$dbmode" == devel -o "${name%.devel.sql}" == "$name" ] || continue [ -z "$updatefile" -o "$updatefile" == "$update" ] || continue 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 setx name=basename "$create" [ "$dbmode" == devel -o "${name%.devel.sql}" == "$name" ] || continue [ -z "$updatefile" -o "$updatefile" == "$update" ] || continue 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 setx name=basename "$update" [ "$dbmode" == devel -o "${name%.devel.sql}" == "$name" ] || continue [ -z "$updatefile" -o "$updatefile" == "$update" ] || continue if have_tag admin "$update"; then [ -n "$updatedir" ] && name="${update#$updatedir/}" || name="${update#$dbdir/}" oracle_admin_update "$name" "$update" "$updatefile" else name="${update#$dbdir/}" oracle_user_update "$name" "$update" "$dbname" "$updatefile" fi done eend; eclearp ############################################################################ else die "BUG: $dbtype: type non implémenté" fi eend done