sqlmig: support de l'importation directe des fichiers csv

This commit is contained in:
Jephté Clain 2019-05-09 12:21:07 +04:00
parent d028c47842
commit d197fc0420
1 changed files with 208 additions and 55 deletions

263
sqlmig
View File

@ -80,13 +80,53 @@ OPTIONS
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 '\\N' et --csv-null NULL respectivement
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
@ -156,14 +196,9 @@ OPTIONS
--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.
Ne pas procéder à la conversion des fichiers CSV en SQL
--force-data-csv
Forcer la conversion des fichiers *-data.csv. Par défaut, la conversion
Forcer la conversion des fichiers CSV en SQL. Par défaut, la conversion
n'est faite que si le fichier csv est plus récent que le fichier sql
correspondant.
--devel-mode
@ -326,12 +361,60 @@ function set_dbdirs() {
fi
}
function fix_csv2sql() {
if [ -z "$csv2sql" ]; then
# valeur par défaut
csv2sql=1
elif is_yes "$csv2sql"; then
csv2sql=1
elif is_no "$csv2sql"; then
csv2sql=0
fi
}
function fix_csv_null() {
# si csv_null a une valeur vide, c'est déjà la valeur par défaut
case "$csv_null" in
empty) csv_null=;;
mysql) csv_null='\N';;
upper) csv_null=NULL;;
esac
}
function fix_load_data() {
if [ -z "$load_data" ]; then
# valeur par défaut
load_data=local
else
case "${load_data,,}" in
default|d*) load_data=default;;
local|l*) load_data=local;;
server|s*) load_data=server;;
esac
fi
if [ "$load_data" == default ]; then
case "$host" in
""|localhost|127.*|::1) load_data=server;;
*) load_data=local;;
esac
fi
}
function get_csvinfo() {
# afficher les informations sur un fichier csv: nom de la table, et s'il
# faut faire un truncate
local csvname="$(basename -- "$1")"
local script='{
truncate = ($0 ~ /-data_truncate(.devel)?.csv$/)? "1": ""
sub(/^.*\//, "")
sub(/^(([A-Z][-.A-Z0-9]*[0-9]-?)|([0-9][-.0-9]*-?))/, "")
sub(/\.csv$/, "")
sub(/\.devel$/, "")
sub(/-data(_[a-z]+*)?$/, "")
print "truncate=" truncate
gsub(/'\''/, "'\'\\\\\'\''")
print "table='\''" $0 "'\''"
}'
awk "$script" <<<"$csvname"
}
################################################################################
# MySQL
@ -476,6 +559,13 @@ function mysql__mconf_get() {
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" ] && {
@ -483,14 +573,27 @@ function mysql__mconf_get() {
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_csv_null
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"
@ -509,6 +612,10 @@ function mysql_set_userargs() {
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=()
@ -561,7 +668,26 @@ function mysql_user_update() {
if [ -z "$updateone" ]; then
mysql_before_update "$dbname" || die
fi
cat "$update" | mysql_user_ve || abort_on_error
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
@ -827,7 +953,7 @@ function oracle_source_adminconf() {
}
function oracle_source_userconf() {
local dir="$1" dbname="$2"
unset ORACLE_SID NLS_LANG ADMINCONNECT USERCONNECT SQLMIGLOG SUFFIX CSV_NULL
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"
@ -851,10 +977,10 @@ function oracle_source_userconf() {
fi
[ -n "$SQLMIGLOG" ] || SQLMIGLOG="/tmp/sqlmig-${ORACLE_SID}-${dbname}.log"
[ -z "$suffix" ] && suffix="$SUFFIX"
[ -z "$csv_null" ] && {
csv_null="$CSV_NULL"
fix_csv_null
}
[ -z "$csv2sql" ] && csv2sql="$CSV2SQL"
[ -z "$csv_null" ] && csv_null="$CSV_NULL"
fix_csv2sql
fix_csv_null
}
ORACLE_ADMIN_CONF_DONE=
function oracle_admin_update() {
@ -918,7 +1044,9 @@ charset=
oracle_sid=
nls_lang=
suffix=
csv2sql=
csv_null=
load_data=
profile=
type=auto
action=update
@ -946,9 +1074,13 @@ args=(
-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
@ -1060,7 +1192,9 @@ default-character-set=utf8
[sqlmig]
#suffix=
csv_null="
#csv2sql=0
csv_null=
#load-data=default"
fi
if [ ! -f "$dbdir/my-${dbname}.cnf" ]; then
@ -1110,7 +1244,8 @@ USERCONNECT=$dbname/password
SQLMIGLOG=\"/tmp/sqlmig-\${ORACLE_SID}-${dbname}.log\"
# divers
#SUFFIX=
#CSV_NULL="
#CSV2SQL=0
CSV_NULL="
fi
else
@ -1289,9 +1424,9 @@ function should_update() {
elif [ "${name#maint-}" != "$name" ]; then
# ignorer les opérations de maintenance par défaut
return 1
elif [ "$dbmode" != devel -a "${name%.devel.sql}" != "$name" ]; then
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.sql
# par .devel.*
return 1
fi
return 0
@ -1347,13 +1482,17 @@ for dbdir in "${dbdirs[@]}"; do
ensure_dbtype "$dbdir" "$type"
ensure_dbmode "$dbtype" "$mode"
if [ -n "$data_csv" ]; then
# Conversion csv --> sql
array_lsfiles csvs "$dbdir" "*.csv"
if [ "$dbtype" == mysql ]; then
setx defaults=mysql_get_defaults "$dbdir"
set_csv_null=1
mysql__mconf_get "$defaults"
if [ "$dbtype" == mysql ]; then
setx defaults=mysql_get_defaults "$dbdir"
set_csv2sql=1
set_csv_null=1
mysql__mconf_get "$defaults"
fix_csv2sql
fix_csv_null
if is_yes "$csv2sql" && [ -n "$data_csv" ]; then
# Conversion csv --> sql
array_lsfiles csvs "$dbdir" "*.csv"
etitled "Conversion"
for csv in "${csvs[@]}"; do
@ -1364,19 +1503,14 @@ for dbdir in "${dbdirs[@]}"; do
fi
estep "$csvname --> ${csvname%.csv}.sql"
script='{
truncate = ($0 ~ /-data_truncate(.devel)?.csv$/)? "1": ""
sub(/^.*\//, "")
sub(/^[A-Z0-9.]*[0-9]-?/, "")
sub(/\.csv$/, "")
sub(/\.devel$/, "")
sub(/-data(_[a-z]+*)?$/, "")
print "truncate=" truncate
gsub(/'\''/, "'\'\\\\\'\''")
print "table='\''" $0 "'\''"
}'
eval "$(awk "$script" <<<"$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"
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
@ -1386,14 +1520,21 @@ for dbdir in "${dbdirs[@]}"; do
drops=()
creates=()
updates=()
array_lsfiles files "$dbdir" "*.sql"
have_csv=
array_lsfiles files "$dbdir" "*.sql" "*.csv"
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
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
@ -1408,6 +1549,9 @@ for dbdir in "${dbdirs[@]}"; do
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
@ -1449,10 +1593,16 @@ for dbdir in "${dbdirs[@]}"; do
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
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
@ -1511,11 +1661,14 @@ for dbdir in "${dbdirs[@]}"; do
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"
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