nutools/sqlmig

1215 lines
41 KiB
Bash
Executable File

#!/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
--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
-u, --update
Mettre à jour la base de données. C'est l'option par défaut
-b, --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.
-n, --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.
--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 suite
de chiffres est transformé en une suite d'insertions dans la table TABLE
après l'avoir vidée.
La variante NUMTABLE-data_insert.csv ajoute les données dans la table
sans la vider d'abord.
--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 __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
}
################################################################################
# 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() {
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
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_set_userargs() {
local dir="$1" dbname="$2" defaults
userargs=()
setx defaults=mysql_get_defaults
if [ -f "$defaults" ]; then
array_add userargs --defaults-file="$defaults"
if [ -n "$userdefaults" ]; then
array_add userargs --defaults-extra-file="$userdefaults"
elif [ -f "$dir/my-${dbname}.cnf" ]; then
array_add userargs --defaults-extra-file="$dir/my-${dbname}.cnf"
fi
elif [ -n "$userdefaults" ]; then
array_add userargs --defaults-file="$userdefaults"
elif [ -f "$dir/my-${dbname}.cnf" ]; then
array_add userargs --defaults-file="$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" done
if [ -z "$MYSQL_ADMIN_CONF_DONE" ]; then
MYSQL_ADMIN_CONF_DONE=1
mysql_tbconf
fi
setx done=mysql_get_done "$name" || die
[ -n "$done" ] && return
estep "$name"
mysql_before_update || die
cat "$update" | mysql_admin_ve || abort_on_error
mysql_after_update || die
}
function mysql_user_update() {
local name="$1" update="$2" dbname="$3" done
setx done=mysql_get_done "$name" "$dbname" || die
[ -n "$done" ] && return
estep "$name"
mysql_before_update "$dbname" || die
cat "$update" | mysql_user_ve || abort_on_error
mysql_after_update "$dbname" || die
}
################################################################################
# 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() {
# 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
[ -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
setx defaults=oracle_get_defaults
[ -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"
}
ORACLE_ADMIN_CONF_DONE=
function oracle_admin_update() {
local name="$1" update="$2" done
if [ -z "$ORACLE_ADMIN_CONF_DONE" ]; then
ORACLE_ADMIN_CONF_DONE=
oracle_tbconf
fi
setx done=oracle_get_done "$name" || die
[ -n "$done" ] && return
estep "$name"
oracle_before_update || die
cat "$update" | oracle_admin_ve || abort_on_error
oracle_after_update || die
}
function oracle_user_update() {
local name="$1" update="$2" dbname="$3" done
setx done=oracle_get_done "$name" "$dbname" || die
[ -n "$done" ] && return
estep "$name"
oracle_before_update "$dbname" || die
cat "$update" | oracle_user_ve || abort_on_error
oracle_after_update "$dbname" || die
}
################################################################################
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=
profile=
type=auto
action=update
updatedir=
exportdir=
dbdir=
dbname=
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=
--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'
-u,--update action=update
-b:,--updatedir: updatedir=
-d:,--databasedir: dbdir=
-n:,--database: dbname=
--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"
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\""
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"
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" "*.sql"
for update in "${updates[@]}"; do
[ -d "$destdir" ] || mkdir -p "$destdir"
copy_update "$update" "$destdir"
done
eend; eclearp
done
exit 0
################################################################################
elif [ "$action" != update ]; then
die "BUG: $action: action non implémentée"
fi
################################################################################
# update
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
# 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
mysql_admin_update "$name" "$update"
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
oracle_admin_update "$name" "$update"
done
eend; eclearp
fi
fi
fi
## mises à jour utilisateur
force_dbname="$dbname"
for dbdir in "${dbdirs[@]}"; do
dbname="$force_dbname"
[ -n "$dbname" ] || setx dbname=basename "$dbdir"
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_insert(.devel)?.csv$/)? "": "1"
sub(/^.*\//, "")
sub(/^[0-9]+/, "")
sub(/\.csv$/, "")
sub(/\.devel$/, "")
sub(/-data(_[a-z]+*)?$/, "")
print "truncate=" truncate
gsub(/'\''/, "'\'\\\\\'\''")
print "table='\''" $0 "'\''"
}' <<<"$csvname")" #"
"$scriptdir/mysqlloadcsv" >"$sql" ${truncate:+-T} -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
# 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
estep "$name"
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
estep "$name"
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
if have_tag admin "$update"; then
mysql_admin_update "$name" "$update"
else
mysql_user_update "$name" "$update" "$dbname"
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
# 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
estep "$name"
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
estep "$name"
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
if have_tag admin "$update"; then
oracle_admin_update "$name" "$update"
else
oracle_user_update "$name" "$update" "$dbname"
fi
done
eend; eclearp
############################################################################
else
die "BUG: $dbtype: type non implémenté"
fi
eend
done