#!/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 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 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