#!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 source "$(dirname -- "$0")/lib/ulib/auto" || exit 1 urequire install function display_help() { uecho "$scriptname: gérer les mises à jour sur une base de données Les mises à jours sont dans un répertoire UPDATEDIR, qui contient un répertoire pour chaque base de données à gérer. Ces répertoires sont des DATABASEDIR. Les fichiers sql peuvent être placés dans le répertoire UPDATEDIR pour les mises à jour administratives, ou dans DATABASEDIR pour les mises à jour de la base de données. Les fichiers sql peuvent contenir dans les premières lignes du fichier un tag @sqlmig sous forme de commentaire. On peut avoir: -- @sqlmig drop identifie une suite de requêtes à exécuter pour supprimer la base de données et recommencer à zéro -- @sqlmig create identifie une suite de requêtes à exécuter pour créer la base de données. -- @sqlmig admin identifie une mise à jour administrative. Les fichiers situés dans UPADTEDIR sont automatiquement considérés comme des mises à jour administratives même en l'absence de ce tag. Les mises à jour administratives sont exécutées avec les paramètres du fichier my.cnf (resp. ora.conf) et sont typiquement utilisées pour créer les comptes utilisateurs pour la connexion à la base de données et leur attribuer des droits d'accès. Ces mises à jour sont exécutées sans sélectionner une base de données en particulier. Les mises à jour utilisateurs sont exécutées avec les paramètres du fichier my.cnf (resp. ora.conf) augmenté du fichier my-DBNAME.cnf (res. ora-DBNAME.conf) Elles sont exécutées avec la base de données concernée sélectionnée. Il y a deux modes d'exécution: prod ou devel. En mode prod, la suppression de base de données est interdite. De plus, tous les fichiers de mise à jour de la forme *.devel.sql sont ignorés. Cela permet de fignoler une mise à jour sans risquer de l'exécuter en production. Les fichiers de la forme maint-*.sql sont ignorés sauf s'ils sont explicitement sélectionnés avec l'option -f ce qui permet de suivre les opérations de maintenance faites sur la base de données. USAGE $scriptname [options] OPTIONS -g, --admin-defaults-file ADMINDEFAULTS Spécifier un fichier de configuration à utiliser pour se connecter à la base de données pour les opérations de maintenance comme la création ou la suppression de la base de données. * Dans le mode MySQL, ce fichier est chargé en plus du fichier par défaut my.cnf * Dans le mode Oracle, ce fichier est chargé en plus du fichier par défaut ora.conf. -C, --defaults-file USERDEFAULTS Spécifier un fichier de configuration à utiliser pour se connecter à la base de données pour les opérations de mise à jour. * Dans le mode MySQL, ce fichier est chargé en plus du fichier par défaut my-DATABASE.cnf * Dans le mode Oracle, ce fichier est chargé en plus du fichier par défaut ora-DATABASE.conf. -u, --user USER -p, --password PASSWORD -h, --host HOST --port PORT --socket SOCKET --character-set CHARSET -s, --oracle-sid ORACLE_SID --nls-lang NLS_LANG Spécifier les valeurs pour la connexion à la base de données. Les options --host, --port et --socket ne sont valides que pour MySQL. Ces valeurs remplacent les valeurs par défaut chargées depuis my.cnf Les options --sid et --nls-lang ne sont valides que pour Oracle. Ces valeurs remplacent les valeurs par défaut chargées depuis ora.conf --suffix SUFFIX Lors de la connexion à la base de données, toujours ajouter le suffixe spécifié au nom de la base de données. Cette valeur peut être spécifiée dans la section [sqlmig] du fichier my.cnf ou avec la variable SUFFIX pour ora.conf --csv2sql Convertir les fichiers CSV en SQL. C'est la valeur par défaut. Ce paramètre peut être spécifié dans my.cnf sous la forme [sqlmig] csv2sql=1 ou dans ora.conf CSV2SQL=1 Pour le moment, cette conversion n'est supportée que pour MySQL. Tous les fichiers de la forme [NUM]TABLE[-data].csv sont transformés en une suite d'insertion dans la table TABLE. Une variante insère les données dans la table après l'avoir vidée avec 'truncate table'. Les fichiers doivent être de la forme [NUM]TABLE-data_truncate.csv --csv-null VALUE Lors de la conversion des fichiers .csv en .sql, considérer que VALUE représente la valeur NULL. Par défaut, utiliser la chaine vide --csv-null-empty --csv-null-mysql --csv-null-upper Aliases pour --csv-null '', --csv-null '\\N' et --csv-null NULL respectivement --no-csv2sql Ne pas convertir les fichiers CSV en SQL. Ce paramètre peut être spécifié dans my.cnf sous la forme [sqlmig] csv2sql=0 ou dans ora.conf CSV2SQL=0 --load-data DEFAULT|LOCAL|SERVER Si l'option --no-csv2sql est activée, spécifier le type de chargement effectué pour les fichiers CSV. * Avec LOCAL (la valeur par défaut), la directive 'load data local' est utilisée. Cependant les erreurs éventuelles sont ignorées parce que le serveur n'a pas de moyen pour arrêter la transmission des informations par le client. * Avec SERVER, la directive 'load data' est utilisée. Les erreurs éventuelles sont affichées et arrêtent l'importation des données. Par contre, le fichier doit être accessible par le serveur MySQL puisque les données sont chargées directement par le serveur. NB: avec MariaDB sur systemd, l'accès à /home, /root et /run/user est désactivé par défaut. Pour pouvoir utiliser ce mode avec des fichiers de l'utilisateur, il faut lancer ces commandes: [ \$(id -u) == 0 ] && sudo= || sudo=sudo \$sudo mkdir -p /etc/systemd/system/mariadb.service.d \$sudo tee /etc/systemd/system/mariadb.service.d/dontprotecthome.conf <<<\$'[Service]\nProtectHome=false' \$sudo systemctl daemon-reload * Avec DEFAULT, utiliser SERVER si on communique avec le serveur MySQL local (c'est à dire si host n'est pas spécifié ou vaut 127.*, ::1 ou localhost). Sinon, utiliser LOCAL --profile PROFILE -P, --prod -T, --test Activer le profil spécifié. Les options -P et -T sont des aliases pour les options --profile prod et --profile test respectivement. Quand un profil est activé, les fichiers de configuration par défaut deviennent respectivement \$PROFILE-my.cnf et \$PROFILE-ora.conf au lieu de my.cnf et ora.conf. De plus, ces fichiers sont aussi recherchés dans le répertoire /etc/sqlmig, en fonction du mode: en mode production, on cherche d'abord dans /etc/sqlmig. En mode développement, on cherche d'abord dans le répertoire local. --mysql --oracle Spécifier le type de base de données à gérer. Par défaut, on gère une base de type mysql. Avec le type oracle, il faut adapter la lecture de cette documentation: à chaque fois que l'on parle de base de données, il s'agit en réalité de gérer un utilisateur. La connexion à la base de données proprement dite est configurée dans le fichier ora.conf -0, --init Créer les fichiers initiaux pour gérer une base de données. Cette option est utilisée pour le développement -e, --export DESTUPDATEDIR Exporter les définitions de bases de données et mises à jour du répertoire courant vers le répertoire DESTUPDATEDIR -c, --connect Se connecter avec le client natif (mysql ou sqlplus) sur la base de données courante. -r, --restore-test Restaurer une sauvegarde d'une base de production sur la test. Cette option n'est (actuellement) supportée que pour MySQL. L'argument attendu est un fichier de sauvegarde DATABASE.sql[.gz] effectué avec la commande mysqldump --databases --add-drop-database DATABASE Dans le fichier spécifié, on remplace toutes les occurences de DATABASE par DATABASE_test, ensuite on lance (re)création de la base de données mysql /dev/null else mysql_user_ve "$@" >&/dev/null fi } function mysql_admin_ve() { mysql_ve "$1" "$2" "${adminargs[@]}" "${mysqlargs[@]}" "${@:3}" } function mysql_admin_qe() { if show_debug; then mysql_admin_ve "$@" elif show_verbose; then mysql_admin_ve "$@" >/dev/null else mysql_admin_ve "$@" >&/dev/null fi } function mysql_tbconf() { [ -n "$fake" ] && return # s'assurer que la table des mises à jour existe local dbname="$1" tb="$2" if [ -z "$dbname" ]; then # admin setx dbname=get_mysql_admindb if ! mysql_admin_qe "select 1" "$dbname"; then mysql_admin_qe "create database $dbname" fi [ -n "$tb" ] || setx tb=get_mysql_admintb "$dbname" if ! mysql_admin_qe "select count(*) from $tb" "$dbname"; then mysql_admin_qe "create table $tb ( name varchar(128) not null primary key ,tem_done int(1) ,date_start datetime ,date_done datetime )" "$dbname" || die "create table $tb" fi else # user [ -n "$tb" ] || setx tb=get_mysql_usertb "$dbname" if ! mysql_user_qe "select count(*) from $tb"; then mysql_user_qe "create table $tb ( name varchar(128) not null primary key ,tem_done int(1) ,date_start datetime ,date_done datetime )" || die "create table $tb" fi fi } function mysql_get_done() { local name="$1" dbname="$2" tb="$3" if [ -z "$dbname" ]; then # admin setx dbname=get_mysql_admindb [ -n "$tb" ] || setx tb=get_mysql_admintb "$dbname" mysql_admin_ve "select name from $tb where name = '$name' and tem_done = 1" "$dbname" -N else # user [ -n "$tb" ] || setx tb=get_mysql_usertb "$dbname" mysql_user_ve "select name from $tb where name = '$name' and tem_done = 1" -N fi } function mysql_before_update() { local dbname="$1" tb="$2" if [ -z "$dbname" ]; then # admin setx dbname=get_mysql_admindb [ -n "$tb" ] || setx tb=get_mysql_admintb "$dbname" mysql_admin_ve "insert into $tb (name, tem_done, date_start) values ('$name', 0, sysdate()) on duplicate key update tem_done = 0, date_start = sysdate(), date_done = null" "$dbname" else # user [ -n "$tb" ] || setx tb=get_mysql_usertb "$dbname" mysql_user_ve "insert into $tb (name, tem_done, date_start) values ('$name', 0, sysdate()) on duplicate key update tem_done = 0, date_start = sysdate(), date_done = null" fi } function mysql_after_update() { local dbname="$1" tb="$2" if [ -z "$dbname" ]; then # admin setx dbname=get_mysql_admindb [ -n "$tb" ] || setx tb=get_mysql_admintb "$dbname" mysql_admin_ve "update $tb set tem_done = 1, date_done = sysdate() where name = '$name'" "$dbname" else # user [ -n "$tb" ] || setx tb=get_mysql_usertb "$dbname" mysql_user_ve "update $tb set tem_done = 1, date_done = sysdate() where name = '$name'" fi } function mysql_get_defaults() { local dir="$1" if [ -n "$profile" ]; then if [ "$dbmode" == devel ]; then if [ -f "$dir/${profile}-my.cnf" ]; then echo "$dir/${profile}-my.cnf"; return elif [ -f "$PROFILEDIR/${profile}-my.cnf" ]; then echo "$PROFILEDIR/${profile}-my.cnf"; return fi else if [ -f "$PROFILEDIR/${profile}-my.cnf" ]; then echo "$PROFILEDIR/${profile}-my.cnf"; return elif [ -f "$dir/${profile}-my.cnf" ]; then echo "$dir/${profile}-my.cnf"; return fi fi fi echo "$dir/my.cnf" } function mysql_set_adminargs() { local dir="$1" defaults adminargs=() setx defaults=mysql_get_defaults "$dir" if [ -f "$defaults" ]; then array_add adminargs --defaults-file="$defaults" if [ -n "$admindefaults" ]; then array_add adminargs --defaults-extra-file="$admindefaults" fi elif [ -n "$admindefaults" ]; then array_add adminargs --defaults-file="$admindefaults" fi [ ${#adminargs[*]} -gt 0 ] || array_add adminargs --default-character-set utf8 } function mysql__mconf_get() { local defaults="$1" tmp if [ -n "$set_suffix" ]; then setx tmp=mconf_get "$defaults" sqlmig suffix [ -n "$tmp" ] && suffix="$tmp" fi if [ -n "$set_csv2sql" ]; then setx tmp=mconf_get "$defaults" sqlmig csv2sql [ -n "$tmp" ] && { csv2sql="$tmp" fix_csv2sql } fi if [ -n "$set_csv_null" ]; then setx tmp=mconf_get "$defaults" sqlmig csv_null [ -n "$tmp" ] && { csv_null="$tmp" fix_csv_null } fi if [ -n "$set_load_data" ]; then if [ -z "$host" ]; then # load_data==default requière host setx host=mconf_get "$defaults" client host fi setx tmp=mconf_get "$defaults" sqlmig load-data [ -n "$tmp" ] && { load_data="$tmp" fix_load_data } fi } function mysql_set_userargs() { local dir="$1" dbname="$2" defaults local set_suffix set_csv2sql set_csv_null set_load_data userargs=() setx defaults=mysql_get_defaults "$dir" [ -z "$suffix" ] && set_suffix=1 [ -z "$csv2sql" ] && set_csv2sql=1 [ -z "$csv_null" ] && set_csv_null=1 [ -z "$load_data" ] && set_load_data=1 if [ -f "$defaults" ]; then array_add userargs --defaults-file="$defaults" mysql__mconf_get "$defaults" if [ -n "$userdefaults" ]; then array_add userargs --defaults-extra-file="$userdefaults" mysql__mconf_get "$userdefaults" elif [ -f "$dir/my-${dbname}.cnf" ]; then array_add userargs --defaults-extra-file="$dir/my-${dbname}.cnf" mysql__mconf_get "$dir/my-${dbname}.cnf" fi elif [ -n "$userdefaults" ]; then array_add userargs --defaults-file="$userdefaults" mysql__mconf_get "$userdefaults" elif [ -f "$dir/my-${dbname}.cnf" ]; then array_add userargs --defaults-file="$dir/my-${dbname}.cnf" mysql__mconf_get "$dir/my-${dbname}.cnf" fi [ ${#userargs[*]} -gt 0 ] || array_add userargs --default-character-set utf8 # initialiser les valeurs par défaut fix_csv2sql fix_csv_null fix_load_data } function mysql_set_mysqlargs() { mysqlargs=() [ -n "$user" ] && array_add mysqlargs -u "$user" [ -n "$pwset" ] && array_add mysqlargs -p"$password" [ -n "$host" ] && array_add mysqlargs -h "$host" [ -n "$port" ] && array_add mysqlargs -P "$port" [ -n "$socket" ] && array_add mysqlargs -S "$socket" [ -n "$charset" ] && array_add mysqlargs --default-character-set "$charset" [ -n "$force" ] && array_add mysqlargs -f array_add mysqlargs -B } MYSQL_ADMIN_CONF_DONE= function mysql_admin_update() { local name="$1" update="$2" updateone="$3" done if [ -z "$updateone" ]; then if [ -z "$MYSQL_ADMIN_CONF_DONE" ]; then MYSQL_ADMIN_CONF_DONE=1 mysql_tbconf fi if ! setx done=mysql_get_done "$name"; then [ -n "$fake" ] || die fi [ -n "$done" ] && return fi estep "$name" [ -n "$fake" ] && return if [ -z "$updateone" ]; then mysql_before_update || die fi cat "$update" | mysql_admin_ve || abort_on_error if [ -z "$updateone" ]; then mysql_after_update || die fi } function mysql_user_update() { local name="$1" update="$2" dbname="$3" updateone="$4" done if [ -z "$updateone" ]; then if ! setx done=mysql_get_done "$name" "$dbname"; then [ -n "$fake" ] || die fi [ -n "$done" ] && return fi estep "$name" [ -n "$fake" ] && return if [ -z "$updateone" ]; then mysql_before_update "$dbname" || die fi if [[ "$update" == *.sql ]]; then # SQL cat "$update" | mysql_user_ve || abort_on_error else # CSV local truncate table local sql eval "$(get_csvinfo "$update")" [ -n "$truncate" ] && sql="truncate table $table;" [ "$load_data" == local ] && local=1 || local= sql="${sql}load data${local:+ local} infile '$update' into table \`$table\` character set 'utf8' fields terminated by ',' optionally enclosed by '\\\"' escaped by '\\\\' lines terminated by '\\n' starting by '' ignore 1 lines ($(<"$update" awk '{ print; exit }'));" echo "$sql" | mysql_user_ve || abort_on_error fi if [ -z "$updateone" ]; then mysql_after_update "$dbname" || die fi } ################################################################################ # Oracle function get_oracle_admindb() { echo "sqlmig_admin_db_"; } function get_oracle_admintb() { echo "sqlmig_updates_"; } function get_oracle_usertb() { echo "${1}_updates_"; } function oracle_sqlplus() { # lancer sqlplus sans affichage superflu, et spooler vers $SQLMIGLOG #local r; set -x #DEBUG echo "\ set pagesize 0 set feedback off set linesize 8192 set tab off whenever oserror exit failure whenever sqlerror exit sql.sqlcode spool '$SQLMIGLOG' append" >"$OPDIR/login.sql" local connect="$1@$ORACLE_SID" sysdba="$2"; shift; shift ORACLE_PATH="$OPDIR" sqlplus -S "$connect" ${sysdba:+as sysdba} "$@" #r=$?; set +x; return $r #DEBUG } function qe() { # lancer la commande $@: si elle retourne un code d'erreur, afficher le # résultat de la commande sur stderr si on est en mode verbeux, sinon ne # rien afficher local r output output="$("$@")"; r=$? if [ ${#output} -gt 0 ]; then show_verbose && echo "$output" 1>&2 fi return $r } function oracle_admin_query() { # lancer sqlplus avec la connexion admin oracle_sqlplus "$ADMINCONNECT" "$ADMINDBA" "$@" } function oracle_admin_ve() { # lancer une requête admin, ne pas masquer le résultat if [ $# -gt 0 ]; then show_debug && edebug "query: $*" oracle_admin_query <<<"$*" else oracle_admin_query fi } function oracle_admin_qe() { # lancer une requête admin en masquant le résultat if [ $# -gt 0 ]; then show_debug && edebug "query: $*" qe oracle_admin_query <<<"$*" else qe oracle_admin_query fi } function oracle_admin_ne() { # lancer une requête admin et retourner vrai si elle affiche un résultat if [ $# -gt 0 ]; then show_debug && edebug "query: $*" [ -n "$(oracle_admin_query <<<"$*")" ] else [ -n "$(oracle_admin_query)" ] fi } function oracle_admin_have_user() { # tester en mode admin si le user $1 existe oracle_admin_ne "select username from all_users where username = '${1^^}';" } function oracle_admin_have_table() { # tester en mode admin si le table $1 existe (avec éventuellement le owner $2) local sql="select table_name from all_tables where table_name = '${1^^}'" [ -n "$2" ] && sql="$sql and owner = '${2^^}'" oracle_admin_ne "$sql;" } function oracle_user_query() { # lancer sqlplus avec la connexion user oracle_sqlplus "$USERCONNECT" "$USERDBA" "$@" } function oracle_user_ve() { # lancer une requête user, ne pas masquer le résultat if [ $# -gt 0 ]; then show_debug && edebug "query: $*" oracle_user_query <<<"$*" else oracle_user_query fi } function oracle_user_qe() { # lancer une requête user en masquant le résultat if [ $# -gt 0 ]; then show_debug && edebug "query: $*" qe oracle_user_query <<<"$*" else qe oracle_user_query fi } function oracle_user_ne() { # lancer une requête user et retourner vrai si elle affiche un résultat if [ $# -gt 0 ]; then show_debug && edebug "query: $*" [ -n "$(oracle_user_query <<<"$*")" ] else [ -n "$(oracle_user_query)" ] fi } function oracle_user_have_user() { # tester en mode user si le user $1 existe oracle_user_ne "select username from all_users where username = '${1^^}';" } function oracle_user_have_table() { # tester en mode user si le table $1 existe (avec éventuellement le owner $2) local sql="select table_name from all_tables where table_name = '${1^^}'" [ -n "$2" ] && sql="$sql and owner = '${2^^}'" oracle_user_ne "$sql;" } function oracle_tbconf() { [ -n "$fake" ] && return # s'assurer que la table des mises à jour existe local dbname="$1" tb="$2" if [ -z "$dbname" ]; then # admin setx dbname=get_oracle_admindb #if ! oracle_admin_have_user "$dbname"; then # oracle_admin_ve "create user $dbname; grant connect to $dbname;" #fi [ -n "$tb" ] || setx tb=get_oracle_admintb "$dbname" local owner="${ADMINCONNECT%%/*}" [ -n "owner" ] || owner=system if ! oracle_admin_have_table "$tb" "$owner"; then oracle_admin_ve "create table $tb ( name varchar(128) not null primary key ,tem_done number(1) ,date_start timestamp ,date_done timestamp );" || die "create table $tb" fi else # user [ -n "$tb" ] || setx tb=get_oracle_usertb "$dbname" local owner="${USERCONNECT%%/*}" [ -n "owner" ] || owner=system if ! oracle_user_have_table "$tb" "$owner"; then oracle_user_ve "create table $tb ( name varchar(128) not null primary key ,tem_done number(1) ,date_start timestamp ,date_done timestamp );" || die "create table $tb" fi fi } function oracle_get_done() { # afficher le nom d'une mise à jour si elle a été appliquée local name="$1" dbname="$2" tb="$3" if [ -z "$dbname" ]; then # admin setx dbname=get_oracle_admindb [ -n "$tb" ] || setx tb=get_oracle_admintb "$dbname" oracle_admin_ve "select name from $tb where name = '$name' and tem_done = 1;" else # user [ -n "$tb" ] || setx tb=get_oracle_usertb "$dbname" oracle_user_ve "select name from $tb where name = '$name' and tem_done = 1;" fi } function oracle_before_update() { # préparer l'exécution d'une mise à jour local dbname="$1" tb="$2" if [ -z "$dbname" ]; then # admin setx dbname=get_oracle_admindb [ -n "$tb" ] || setx tb=get_oracle_admintb "$dbname" oracle_admin_ve "\ merge into $tb d using (select '$name' name from dual) s on (d.name = s.name) when matched then update set d.tem_done = 0, d.date_start = sysdate, d.date_done = null when not matched then insert (name, tem_done, date_start) values ('$name', 0, sysdate); commit;" else # user [ -n "$tb" ] || setx tb=get_oracle_usertb "$dbname" oracle_user_ve "\ merge into $tb d using (select '$name' name from dual) s on (d.name = s.name) when matched then update set d.tem_done = 0, d.date_start = sysdate, d.date_done = null when not matched then insert (name, tem_done, date_start) values ('$name', 0, sysdate); commit;" fi } function oracle_after_update() { # valider l'exécution d'une mise à jour local dbname="$1" tb="$2" if [ -z "$dbname" ]; then # admin setx dbname=get_oracle_admindb [ -n "$tb" ] || setx tb=get_oracle_admintb "$dbname" oracle_admin_ve "\ update $tb set tem_done = 1, date_done = sysdate where name = '$name'; commit;" else # user [ -n "$tb" ] || setx tb=get_oracle_usertb "$dbname" oracle_user_ve "\ update $tb set tem_done = 1, date_done = sysdate where name = '$name'; commit;" fi } function oracle_ensure_opdir() { if [ -z "$OPDIR" ]; then ac_set_tmpdir OPDIR >"$SQLMIGLOG" fi } function oracle_get_defaults() { local dir="$1" if [ -n "$profile" ]; then if [ "$dbmode" == devel ]; then if [ -f "$dir/${profile}-ora.conf" ]; then echo "$dir/${profile}-ora.conf"; return elif [ -f "$PROFILEDIR/${profile}-ora.conf" ]; then echo "$PROFILEDIR/${profile}-ora.conf"; return fi else if [ -f "$PROFILEDIR/${profile}-ora.conf" ]; then echo "$PROFILEDIR/${profile}-ora.conf"; return elif [ -f "$dir/${profile}-ora.conf" ]; then echo "$dir/${profile}-ora.conf"; return fi fi fi echo "$dir/ora.conf" } function oracle_source_adminconf() { local dir="$1" unset ORACLE_SID NLS_LANG ADMINCONNECT USERCONNECT SQLMIGLOG setx defaults=oracle_get_defaults "$dir" [ -f "$defaults" ] && source "$defaults" [ -n "$admindefaults" ] && { source "$admindefaults" || die } [ -n "$oracle_sid" ] && ORACLE_SID="$oracle_sid" [ -n "$nls_lang" ] && NLS_LANG="$nls_lang" export ORACLE_SID NLS_LANG if [ -n "$ADMINCONNECT" ]; then ADMINDBA= else ADMINCONNECT=/ ADMINDBA=1 fi [ -n "$SQLMIGLOG" ] || SQLMIGLOG="/tmp/sqlmig-${ORACLE_SID}-${dbname}.log" } function oracle_source_userconf() { local dir="$1" dbname="$2" unset ORACLE_SID NLS_LANG ADMINCONNECT USERCONNECT SQLMIGLOG SUFFIX CSV2SQL CSV_NULL setx defaults=oracle_get_defaults "$dir" [ -f "$defaults" ] && source "$defaults" [ -f "$dir/ora-${dbname}.conf" ] && source "$dir/ora-${dbname}.conf" [ -n "$userdefaults" ] && { source "$userdefaults" || die } [ -n "$oracle_sid" ] && ORACLE_SID="$oracle_sid" [ -n "$nls_lang" ] && NLS_LANG="$nls_lang" export ORACLE_SID NLS_LANG if [ -n "$ADMINCONNECT" ]; then ADMINDBA= else ADMINCONNECT=/ ADMINDBA=1 fi if [ -n "$USERCONNECT" ]; then USERDBA= else USERCONNECT=/ USERDBA=1 fi [ -n "$SQLMIGLOG" ] || SQLMIGLOG="/tmp/sqlmig-${ORACLE_SID}-${dbname}.log" [ -z "$suffix" ] && suffix="$SUFFIX" [ -z "$csv2sql" ] && csv2sql="$CSV2SQL" [ -z "$csv_null" ] && csv_null="$CSV_NULL" fix_csv2sql fix_csv_null } ORACLE_ADMIN_CONF_DONE= function oracle_admin_update() { local name="$1" update="$2" updateone="$3" done if [ -z "$updateone" ]; then if [ -z "$ORACLE_ADMIN_CONF_DONE" ]; then ORACLE_ADMIN_CONF_DONE= oracle_tbconf fi setx done=oracle_get_done "$name" || die [ -n "$done" ] && return fi estep "$name" [ -n "$fake" ] && return if [ -z "$updateone" ]; then oracle_before_update || die fi cat "$update" | oracle_admin_ve || abort_on_error if [ -z "$updateone" ]; then oracle_after_update || die fi } function oracle_user_update() { local name="$1" update="$2" dbname="$3" updateone="$4" done if [ -z "$updateone" ]; then setx done=oracle_get_done "$name" "$dbname" || die [ -n "$done" ] && return fi estep "$name" [ -n "$fake" ] && return if [ -z "$updateone" ]; then oracle_before_update "$dbname" || die fi cat "$update" | oracle_user_ve || abort_on_error if [ -z "$updateone" ]; then oracle_after_update "$dbname" || die fi } ################################################################################ MODE=auto PROFILEDIR=/etc/sqlmig prod_PROFILE_MODE=prod test_PROFILE_MODE=devel set_defaults sqlmig admindefaults= userdefaults= user= password= pwset= host= port= socket= charset= oracle_sid= nls_lang= suffix= csv2sql= csv_null= load_data= profile= type=auto action=update updatedir= exportdir= dbdir= dbname= updateone= force= data_csv=auto mode="$MODE" drop= drop_only= create_only= args=( --help '$exit_with display_help' -g:,--admin-defaults-file: admindefaults= -C:,--defaults-file: userdefaults= -u:,--user: user= -p:,--password: '$set@ password; pwset=1' -h:,--host: host= --port: port= --socket: socket= --character-set: charset= -s:,--oracle-sid: oracle_sid= --nls-lang: nls_lang= --suffix: suffix= --csv2sql csv2sql=1 --csv-null: csv_null= --csv-null-empty csv_null=empty --csv-null-mysql csv_null='\N' --csv-null-upper csv_null=NULL --no-csv2sql csv2sql=0 --load-data: load_data= --profile: profile= -P,--prod profile=prod -T,--test profile=test --mysql type=mysql --oracle type=oracle -0,--init action=init -e:,--export: '$action=export; set@ exportdir' -c,--connect action=connect -r,--restore-test action=restore_test --update-all action=update -t:,--updatedir: updatedir= -d:,--databasedir: dbdir= -b:,--database: dbname= -f,--update-one updateone=1 -n,--fake fake=1 --force,--continue-on-error force=1 --no-data-csv data_csv= --force-data-csv data_csv=force --prod-mode-dangerous mode=prod --devel-mode mode=devel -Z,--recreate drop=1 --drop-only '$drop=1; drop_only=1' --create-only create_only=1 ) parse_args "$@"; set -- "${args[@]}" setx cwd=pwd if [ -z "$dbdir" -a -z "$updatedir" ]; then setx parentdir=dirname -- "$cwd" if __check_devel_dir src/main/resources/database; then enote "Autosélection src/main/resources/database/" elif __check_devel_dir config/sqlmig; then enote "Autosélection config/sqlmig/" elif __check_devel_dir support/database; then enote "Autosélection support/database/" elif __check_devel_dir database; then enote "Autosélection database/" elif __check_mysql_prod_dir; then [ "$type" == auto ] && type=mysql fi fi [ -n "$updatedir" ] && setx updatedir=abspath "$updatedir" [ -n "$dbdir" ] && setx dbdir=abspath "$dbdir" ################################################################################ if [ "$action" == init ]; then [ -n "$dbdir" -a -z "$dbname" ] && setx dbname=basename "$dbdir" [ -n "$dbname" ] || dbname="$1" read_value ${dbname:+-i} "Entrez le nom de la base de données" dbname "$dbname" if [ -z "$dbdir" -a -n "$updatedir" ]; then dbdir="$updatedir/$dbname" elif [ -z "$dbdir" ]; then dbdir="$dbname" fi read_value ${dbdir:+-i} "Entrez le répertoire dans lequel créer les définitions" dbdir "$dbdir" setx dbdir=abspath "$dbdir" ask_yesno "Voulez-vous créer les fichiers initiaux pour la base de données $dbname dans le répertoire $(ppath "$dbdir")?" O || die estep "Création du répertoire $dbdir" [ -d "$dbdir" ] || mkdir -p "$dbdir" || die [ "$type" == auto ] && type=mysql if [ "$type" == mysql ]; then if [ ! -f "$dbdir/00dropdb.sql" ]; then estep "00dropdb.sql" echo >"$dbdir/00dropdb.sql" "\ -- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -- @sqlmig drop drop database if exists @@database@@;" fi if [ ! -f "$dbdir/01createdb.sql" ]; then estep "01createdb.sql" echo >"$dbdir/01createdb.sql" "\ -- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -- @sqlmig create create database @@database@@;" fi if [ ! -f "$dbdir/02grants.sql" ]; then estep "02grants.sql" echo >"$dbdir/02grants.sql" "\ -- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -- @sqlmig admin create user '$dbname' identified by '$dbname'; grant all privileges on $dbname.* to '$dbname';" fi if [ ! -f "$dbdir/my.cnf" ]; then estep "my.cnf" echo >"$dbdir/my.cnf" "\ # Paramètres de connexion par défaut [client] #user= #password= #host=localhost #port=3306 #socket=/var/run/mysqld/mysqld.sock [mysql] default-character-set=utf8 [sqlmig] #suffix=" fi if [ ! -f "$dbdir/my-${dbname}.cnf" ]; then estep "my-${dbname}.cnf" echo >"$dbdir/my-${dbname}.cnf" "\ # Paramètres de connexion pour $dbname [client] #user= #password= [sqlmig] #csv2sql=0 csv_null= #load-data=default" fi elif [ "$type" == oracle ]; then if [ ! -f "$dbdir/00dropuser.sql" ]; then estep "00dropuser.sql" echo >"$dbdir/00dropuser.sql" "\ -- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -- @sqlmig drop drop user $dbname cascade;" fi if [ ! -f "$dbdir/01createuser.sql" ]; then estep "01createuser.sql" echo >"$dbdir/01createuser.sql" "\ -- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -- @sqlmig create create user $dbname identified by \"$dbname\"; grant connect to $dbname; grant resource to $dbname;" fi if [ ! -f "$dbdir/ora.conf" ]; then estep "ora.conf" echo >"$dbdir/ora.conf" "\ # Paramètres de connexion par défaut ORACLE_SID=orcl NLS_LANG=FRENCH_FRANCE.AL32UTF8 #NLS_LANG=AMERICAN_AMERICA.AL32UTF8 # paramètres de connexion pour les mises à jour administratives # si aucune valeur n'est spécifiée, la valeur effective est '/ as sysdba' mais # cela requière que la mise à jour soit faite avec une connexion locale. ADMINCONNECT= # paramètres de connexion pour les mises à jour utilisateur USERCONNECT=$dbname/password # logs des mises à jour SQLMIGLOG=\"/tmp/sqlmig-\${ORACLE_SID}-${dbname}.log\" # divers #SUFFIX= #CSV2SQL=0 CSV_NULL=" fi else die "BUG: $type: type non implémenté" fi exit 0 ################################################################################ elif [ "$action" == export ]; then [ -n "$exportdir" ] || setx exportdir=find_mysqldupdatedir check_mysqldupdatedir "$exportdir" setx exportdir=abspath "$exportdir/updates" if [ -n "$dbdir" ]; then dbdirs=("$dbdir") elif [ -z "$updatedir" ]; then array_lsfiles files . "*.sql" "*my.cnf" "my-*.cnf" "*ora.conf" "ora-*.conf" if [ ${#files[*]} -gt 0 ]; then enote "Autosélection répertoire courant" dbdir="$cwd" dbdirs=("$dbdir") else die "Vous devez spécifier l'option -b" fi elif [ -n "$dbname" ]; then dbdirs=("$updatedir/$dbname") else array_lsdirs dbdirs "$updatedir" fi for dbdir in "${dbdirs[@]}"; do setx dbname=basename -- "$dbdir" # TEMPLATE.d est spécial dans mysqld-update: il faut l'ignorer [ "$dbname" != TEMPLATE.d ] || continue etitled "$dbname" destdir="$exportdir/$dbname" array_lsfiles updates "$dbdir" for update in "${updates[@]}"; do [ -d "$destdir" ] || mkdir -p "$destdir" [[ "$update" == "*.devel.sql" ]] && continue copy_update "$update" "$destdir" done eend; eclearp done exit 0 ################################################################################ elif [ "$action" == connect ]; then set_dbdirs force_dbname="$dbname" force_suffix="$suffix" for dbdir in "${dbdirs[@]}"; do dbname="$force_dbname" [ -n "$dbname" ] || setx dbname=basename "$dbdir" suffix="$force_suffix" etitle "$dbname" ensure_dbtype "$dbdir" "$type" ensure_dbmode "$dbtype" "$mode" if [ "$dbtype" == mysql ]; then # construire les paramètres pour mysql mysql_set_userargs "$dbdir" "$dbname" mysql_set_mysqlargs if [ -n "$suffix" ]; then estepi "Suffixe: $dbname --> $dbname$suffix" dbname="$dbname$suffix" fi array_del mysqlargs -B # désactiver le mode batch mysql "${userargs[@]}" "${mysqlargs[@]}" -D "$dbname" elif [ "$dbtype" == oracle ]; then # lire les paramètres oracle_source_userconf "$dbdir" "$dbname" oracle_ensure_opdir if [ -n "$suffix" ]; then estepi "Suffixe: $dbname --> $dbname$suffix" dbname="$dbname$suffix" fi sqlplus "$USERCONNECT@$ORACLE_SID" ${USERDBA:+as sysdba} "$@" else die "BUG: $dbtype: type non implémenté" fi eend done exit 0 ################################################################################ elif [ "$action" == restore_test ]; then set_dbdirs if [ ${#dbdirs[*]} -gt 1 ]; then die "Avec --restore-test, une seule base de données doit être spécifiée" fi dbdir="${dbdirs[0]}" [ -n "$dbname" ] || setx dbname=basename "$dbdir" etitle "$dbname" ensure_dbtype "$dbdir" "$type" ensure_dbmode "$dbtype" "$mode" [ "$dbtype" == mysql ] || die "Seule les bases de type MySQL sont supportées" # construire les paramètres pour mysql mysql_set_userargs "$dbdir" "$dbname" mysql_set_mysqlargs if [ -n "$suffix" ]; then estepi "Suffixe: $dbname --> $dbname$suffix" dbname="$dbname$suffix" fi dump="$1" [ -n "$dump" ] || die "Vous devez spécifier le fichier de dump" [ -f "$dump" ] || die "$dump: fichier de dump introuvable" ac_set_tmpdir tmpdir if [[ "$dump" == *.gz ]]; then gzip -dc "$dump" >"$tmpdir/prod.sql" elif [[ "$dump" == *.sql ]]; then cat "$dump" >"$tmpdir/prod.sql" else die "$dump: n'est pas un fichier sql" fi dump="$tmpdir/prod.sql" pname="$dbname" tname="${dbname}_test" sed <"$tmpdir/prod.sql" >"$tmpdir/test.sql" "\ s/\`$pname\`/\`$tname\`/g s/\`${pname}_updates_\`/\`${tname}_updates_\`/g" enote "Vous allez restaurer un fichier de sauvegarde de le base $pname vers la base $tname" ask_yesno "Voulez-vous continuer?" X || die array_del mysqlargs -B # désactiver le mode batch mysql "${userargs[@]}" "${mysqlargs[@]}" <"$tmpdir/test.sql" ac_clean "$tmpdir" exit 0 ################################################################################ elif [ "$action" != update ]; then die "BUG: $action: action non implémentée" fi ################################################################################ # update set_dbdirs if [ -n "$updateone" ]; then updatefiles=() for updatefile in "$@"; do array_add updatefiles "$(abspath "$updatefile")" done fi function should_update() { local update="$1" name setx name=basename "$update" if [ -n "$updateone" ]; then # prendre tous les fichiers sélectionnés par -f array_contains updatefiles "$update" return $? elif [ "${name#maint-}" != "$name" ]; then # ignorer les opérations de maintenance par défaut return 1 elif [ "$dbmode" != devel -a "${name%.devel.*}" != "$name" ]; then # si on est en mode autre que devel, le nom ne doit pas se terminer # par .devel.* return 1 fi return 0 } # répertoire temporaire pour Oracle OPDIR= if [ -n "$updatedir" ]; then ## mises à jour administratives array_lsfiles updates "$updatedir" "*.sql" if [ ${#updates[*]} -gt 0 ]; then ensure_dbtype "$updatedir" "$type" ensure_dbmode "$dbtype" "$mode" if [ "$dbtype" == mysql ]; then # construire les paramètres pour mysql mysql_set_adminargs "$updatedir" mysql_set_mysqlargs # Mises à jour etitled "Mises à jour admin" for update in "${updates[@]}"; do should_update "$update" || continue mysql_admin_update "${update#$updatedir/}" "$update" "$updateone" done eend; eclearp elif [ "$dbtype" == oracle ]; then # lire les paramètres oracle_source_adminconf "$updatedir" oracle_ensure_opdir # Mises à jour etitled "Mises à jour" for update in "${updates[@]}"; do should_update "$update" || continue oracle_admin_update "${update#$updatedir/}" "$update" "$updateone" done eend; eclearp fi fi fi ## mises à jour utilisateur force_dbname="$dbname" force_suffix="$suffix" for dbdir in "${dbdirs[@]}"; do dbname="$force_dbname" [ -n "$dbname" ] || setx dbname=basename "$dbdir" suffix="$force_suffix" etitle "$dbname" ensure_dbtype "$dbdir" "$type" ensure_dbmode "$dbtype" "$mode" ############################################################################ if [ "$dbtype" == mysql ]; then # construire les paramètres pour mysql mysql_set_adminargs "$dbdir" mysql_set_userargs "$dbdir" "$dbname" mysql_set_mysqlargs if is_yes "$csv2sql" && [ -n "$data_csv" ]; then # Conversion csv --> sql array_lsfiles csvs "$dbdir" "*.csv" etitled "Conversion" for csv in "${csvs[@]}"; do setx csvname=basename -- "$csv" sql="${csv%.csv}.sql" if [ "$data_csv" != force ]; then testnewer "$csv" "$sql" || continue fi estep "$csvname --> ${csvname%.csv}.sql" eval "$(get_csvinfo "$csvname")" mysqlloadcsv_args=( ${truncate:+-T} -Z "$csv_null" --prefix "-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8" -nIf "$csv" "$table" ) "$scriptdir/mysqlloadcsv" "${mysqlloadcsv_args[@]}" >"$sql" done eend; eclearp fi # lister les mises à jour disponibles drops=() creates=() updates=() have_csv= array_lsfiles files "$dbdir" "*.sql" "*.csv" for file in "${files[@]}"; do if [[ "$file" == *.sql ]]; then if have_tag drop "$file"; then array_add drops "$file" elif have_tag create "$file"; then array_add creates "$file" else array_add updates "$file" fi elif [ ! -f "${file%.csv}.sql" ]; then # n'ajouter le CSV que si le fichier SQL correspondant n'existe pas array_add updates "$file" have_csv=1 fi done if [ -n "$suffix" ]; then estepi "Suffixe: $dbname --> $dbname$suffix" dbname="$dbname$suffix" fi if is_no "$csv2sql" && [ -n "$have_csv" ]; then estepi "Chargement des fichiers CSV avec la méthode $load_data" fi # Suppression if [ -n "$drop" ]; then [ "$dbmode" == devel ] || die "La suppression de base de données n'est pas autorisée en mode production" etitle "Suppression" if ! ask_yesno "Etes-vous sûr de vouloir supprimer la base de données $dbname?" X; then ewarn "Suppression annulée, les autres opérations ne seront pas effectuées" eend; continue fi for drop in "${drops[@]}"; do should_update "$update" || continue setx name=basename "$drop" estep "$name" [ -n "$fake" ] && continue sed "s/@@database@@/$dbname/g" "$drop" | mysql_admin_qe || abort_on_error "drop: $name" done eend [ -n "$drop_only" ] && { eend; continue; } fi # Création if ! mysql_admin_qe "select 1" "$dbname"; then etitled "Création" for create in "${creates[@]}"; do should_update "$update" || continue setx name=basename "$create" estep "$name" [ -n "$fake" ] && continue sed "s/@@database@@/$dbname/g" "$create" | mysql_admin_qe || abort_on_error "create: $name" done eend; eclearp fi [ -n "$create_only" ] && { eend; continue; } # Mises à jour etitled "Mises à jour" mysql_tbconf "$dbname" for update in "${updates[@]}"; do should_update "$update" || continue if [[ "$update" == *.sql ]]; then # fichier SQL if have_tag admin "$update"; then [ -n "$updatedir" ] && name="${update#$updatedir/}" || name="${update#$dbdir/}" mysql_admin_update "$name" "$update" "$updateone" else mysql_user_update "${update#$dbdir/}" "$update" "$dbname" "$updateone" fi elif is_no "$csv2sql"; then # fichier CSV, ne les traiter que si on est en mode --no-csv2sql mysql_user_update "${update#$dbdir/}" "$update" "$dbname" "$updateone" fi done eend; eclearp ############################################################################ elif [ "$dbtype" == oracle ]; then [ -n "$OPDIR" ] || ac_set_tmpdir OPDIR # lire les paramètres oracle_source_userconf "$dbdir" "$dbname" oracle_ensure_opdir # lister les mises à jour disponibles drops=() creates=() updates=() have_csv= array_lsfiles files "$dbdir" "*.sql" "*.csv" for file in "${files[@]}"; do if [[ "$file" == *.sql ]]; then if have_tag drop "$file"; then array_add drops "$file" elif have_tag create "$file"; then array_add creates "$file" else array_add updates "$file" fi elif [ ! -f "${file%.csv}.sql" ]; then # n'ajouter le CSV que si le fichier SQL correspondant n'existe pas array_add updates "$file" have_csv=1 fi done if [ -n "$suffix" ]; then estepi "Suffixe: $dbname --> $dbname$suffix" dbname="$dbname$suffix" fi # Suppression if [ -n "$drop" ]; then [ "$dbmode" == devel ] || die "La suppression de user n'est pas autorisée en mode production" etitle "Suppression" if ! ask_yesno "Etes-vous sûr de vouloir supprimer le user $dbname?" X; then ewarn "Suppression annulée, les autres opérations ne seront pas effectuées" eend; continue fi for drop in "${drops[@]}"; do should_update "$update" || continue setx name=basename "$drop" estep "$name" [ -n "$fake" ] && continue sed "s/@@database@@/$dbname/g" "$drop" | oracle_admin_ve || abort_on_error "drop: $name" done eend [ -n "$drop_only" ] && { eend; continue; } fi # Création if ! oracle_admin_have_user "$dbname"; then etitled "Création" for create in "${creates[@]}"; do should_update "$update" || continue setx name=basename "$create" estep "$name" [ -n "$fake" ] && continue sed "s/@@database@@/$dbname/g" "$create" | oracle_admin_ve || abort_on_error "create: $name" done eend; eclearp fi [ -n "$create_only" ] && { eend; continue; } # Mises à jour etitled "Mises à jour" oracle_tbconf "$dbname" for update in "${updates[@]}"; do should_update "$update" || continue if [[ "$update" == *.sql ]]; then # fichier SQL if have_tag admin "$update"; then [ -n "$updatedir" ] && name="${update#$updatedir/}" || name="${update#$dbdir/}" oracle_admin_update "$name" "$update" "$updateone" else oracle_user_update "${update#$dbdir/}" "$update" "$dbname" "$updateone" fi fi done eend; eclearp ############################################################################ else die "BUG: $dbtype: type non implémenté" fi eend done