2013-08-27 15:14:44 +04:00
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
2014-07-07 22:06:38 +04:00
source "$(dirname "$0")/lib/ulib/ulib" || exit 1
urequire DEFAULTS awk
2013-08-27 15:14:44 +04:00
function display_help() {
uecho "$scriptname: Charger une table MySQL avec un fichier csv
USAGE
$scriptname [db.]table [fields...] [-- mysql options]
db est le nom de la base de données
table est le nom de la table à charger
fields est la liste des colonnes. Si cette valeur est spécifiée, il faudra
peut-être utiliser l'option -s pour ignorer le cas échéant la ligne des
en-têtes dans le fichier en entrée. Sinon, les colonnes à utiliser sont
calculées à partir du fichier en entrée.
Dans les données en entrées, qui doivent être en UTF8, les conversions suivantes
2014-02-27 14:35:14 +04:00
sont effectuées par MySQL:
2013-08-27 15:14:44 +04:00
2014-02-27 14:35:14 +04:00
\\0 --> <caractère NUL>
\\b --> <backspace>
\\n --> <newline>
\\r --> <carriage return>
\\t --> <tab>
2013-08-27 15:14:44 +04:00
\\Z --> Ctrl+Z
\\N --> NULL
OPTIONS
2014-03-03 17:08:49 +04:00
-h, --host host
-P, --port port
-u, --user user
2013-08-27 15:14:44 +04:00
-ppassword
Informations de connexion à la base de données
2014-03-03 17:08:49 +04:00
-C, --config CONFIG
2013-08-27 15:14:44 +04:00
Prendre les informations de connexion depuis le fichier spécifié.
Le fichier doit être de la forme
host=HOST.TLD
#post=3306
user=USER
password=PASS
#dbtable=DB.TABLE
#fields=(FIELDS...)
# Il est possible aussi de spécifier DB et TABLE séparément:
#database=DB
#table=TABLE
Les variables port, dbtable et fields sont facultatives.
Les valeurs définies dans ce fichier sont prioritaires par rapport à
celles qui auraient été spécifiées sur la ligne de commande.
Utiliser password=--NOT-SET-- s'il faut se connecter sans mot de passe
Cette option peut être utilisée plusieurs fois, auquel cas les fichiers
sont chargés dans l'ordre.
--profile PROFILE
La variable \$PROFILE est définie avec la valeur spécifiée avant de
sourcer les fichiers de configuration. Cela permet d'avoir des fichiers
de configuration qui calculent dynamiquement les paramètres en fonction
de la valeur du profil.
2014-03-03 17:08:49 +04:00
-f, --input INPUT
2013-08-27 15:14:44 +04:00
Fichier en entrée. Ne pas spécifier cette option ou utiliser '-' pour
lire depuis l'entrée standard.
2015-03-06 10:01:59 +04:00
-d, --auto-dbtable DB
Spécifier la base de données avec laquelle se connecter. De plus, si le
nom de la table n'est pas spécifié, prendre par défaut le nom de base du
fichier spécifié avec l'option -f
2014-03-03 17:08:49 +04:00
-s, --skip-lines NBLINES
2014-01-22 15:42:11 +04:00
Nombre de lignes à sauter dans le fichier en entrée
2014-03-03 17:08:49 +04:00
-n, --fake
Ne pas effectuer l'opération. Afficher simplement la commande SQL.
2014-01-22 15:42:11 +04:00
-T, --truncate
2014-02-27 14:35:14 +04:00
Vider la table avant d'effectuer le chargement
2014-03-03 17:08:49 +04:00
-L, --load-data
Charger les données avec la commande 'load data local'. C'est l'option
par défaut.
-I, --insert-data
Charger les données en générant des commandes 'insert into'. L'effet est
en principe le même avec l'option -L (sauf que certains formats de date
exotiques seront correctement importés). Cette option peut aussi être
utilisée avec l'option -n pour générer une liste de commande à corriger
et/ou adapter.
-U, -k, --update-data KEY
2014-02-27 14:35:14 +04:00
Au lieu de charger de nouvelles données, essayer de mettre à jour la
2014-03-03 17:08:49 +04:00
table. KEY est le nom de la colonne qui est utilisée comme clé. Toutes
les autres colonnes sont les nouvelles données à mettre à jour. Si
aucune ligne ne correspond à une clé donnée, la mise à jour pour cette
ligne est ignorée.
Note: utiliser les options -T et -U ensemble n'a pas de sens, mais -T
est quand même honoré."
2013-08-27 15:14:44 +04:00
}
2014-03-03 17:08:49 +04:00
__AWK_MYSQLFUNCS='
function format_sqlvalue(value) {
2015-03-06 09:54:33 +04:00
if (value == "\\N") {
value = "NULL"
} else if (value ~ /^[0-9]+$/) {
2014-03-03 17:08:49 +04:00
} else if (value ~ /^[0-9][0-9]\/[0-9][0-9]\/[0-9][0-9]$/) {
value = "str_to_date('\''" value "'\'', '\''%d/%m/%y'\'')"
} else if (value ~ /^[0-9][0-9]\/[0-9][0-9]\/[0-9][0-9][0-9][0-9]$/) {
value = "str_to_date('\''" value "'\'', '\''%d/%m/%Y'\'')"
} else if (value ~ /^[0-9][0-9][0-9][0-9]\/[0-9][0-9]\/[0-9][0-9]$/) {
value = "str_to_date('\''" value "'\'', '\''%Y/%m/%d'\'')"
} else if (value ~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/) {
value = "str_to_date('\''" value "'\'', '\''%Y-%m-%d'\'')"
} else {
gsub(/'\''/, "'\'\''", value)
value = "'\''" value "'\''"
}
return value
}
'
2013-08-27 15:14:44 +04:00
host=
port=
user=
password=--NOT-SET--
configs=()
profile=
input=
2015-03-06 10:01:59 +04:00
auto_db=
2013-08-27 15:14:44 +04:00
skip_lines=
2014-01-22 15:42:11 +04:00
truncate=
2014-02-27 14:35:14 +04:00
fake=
2014-03-03 17:08:49 +04:00
method=load
update_key=
2013-08-27 15:14:44 +04:00
parse_opts "${PRETTYOPTS[@]}" \
--help '$exit_with display_help' \
-h:,-H:,--host: host= \
-P:,--port: port= \
-u:,--user: user= \
-p::,--passwd:,--password: password= \
-C:,--config: configs \
-f:,--input: input= \
2015-03-06 10:01:59 +04:00
-d:,--auto-dbtable: auto_db= \
2013-08-27 15:14:44 +04:00
-s:,--skip-lines: skip_lines= \
2014-02-27 14:35:14 +04:00
-n,--fake fake=1 \
2014-03-03 17:08:49 +04:00
-T,--truncate truncate=1 \
-L,--load-data method=load \
-I,--insert-data method=insert \
-U:,-k:,--update-data: '$method=update; set@ update_key' \
2013-08-27 15:14:44 +04:00
@ args -- "$@" && set -- "${args[@]}" || die "$args"
dbtable="$1"; shift
fields=()
while [ "$#" -gt 0 -a "$1" != "--" ]; do
fields=("${fields[@]}" "$1")
shift
done
[ "$1" == "--" ] && shift
2015-03-06 10:01:59 +04:00
splitfsep2 "$dbtable" . database table
if [ -z "$database" -a -n "$auto_db" ]; then
database="$auto_db"
if [ -z "$table" -a -n "$input" -a "$input" != "-" ]; then
table="${input%.*}"
fi
2013-08-27 15:14:44 +04:00
fi
if [ -n "${configs[*]}" ]; then
PROFILE="$profile"
array_fix_paths configs
for config in "${configs[@]}"; do
[ -f "$config" ] || die "Fichier introuvable: $config"
source "$(abspath "$config")"
done
fi
[ -n "$table" ] || die "Vous devez spécifier la table dans laquelle se fera l'importation"
2014-01-22 17:31:18 +04:00
if [ -n "$input" -a "$input" != "-" ]; then
[ -f "$input" ] || die "$input: fichier introuvable"
fi
2013-08-27 15:14:44 +04:00
isnum "$skip_lines" || skip_lines=0
if [ -n "${fields[*]}" ]; then
# nous savons quels champs utiliser
cfields="$(array_join fields ,)"
if [ -z "$input" -o "$input" == "-" ]; then
ac_set_tmpfile input
awkcsv -s "$skip_lines" -k "$cfields" >"$input"
else
ac_set_tmpfile tmpinput
<"$input" awkcsv -s "$skip_lines" -k "$cfields" >"$tmpinput"
input="$tmpinput"
fi
else
# les champs seront calculés à partir de l'entrée
if [ -z "$input" -o "$input" == "-" ]; then
ac_set_tmpfile input
cat >"$input"
fi
array_split fields "$(awkcsv -s "$skip_lines" -e '{ print array_join(HEADERS, ","); exit }' <"$input")" ","
cfields="$(array_join fields ,)"
fi
mysqlargs=(
${host:+-h "$host"} ${port:+-P "$port"}
2014-01-22 15:42:11 +04:00
${user:+-u "$user"} ${database:+-D "$database"}
2013-08-27 15:14:44 +04:00
)
[ "$password" != "--NOT-SET--" ] && mysqlargs=("${mysqlargs[@]}" -p"$password")
2014-03-03 17:08:49 +04:00
[ -n "$truncate" ] && truncate="truncate table \`$table\`;"
if [ "$method" == load ]; then
skip_lines=$(($skip_lines + 1))
loadcsv="load data local infile '$input' into table \`$table\` character set 'utf8' fields terminated by ',' optionally enclosed by '\\\"' escaped by '\\\\' lines terminated by '\\n' starting by '' ignore $skip_lines lines ($cfields);"
mysqlargs=("${mysqlargs[@]}"
"$truncate$loadcsv"
--
--local-infile=1
)
cmd=("$scriptdir/mysqlcsv" "${mysqlargs[@]}" "$@")
r=0
if [ -n "$fake" ]; then
echo "-- Requêtes SQL:"
[ -n "$truncate" ] && echo "$truncate"
echo "$loadcsv"
echo "-- Commande à lancer pour importer la table dans MySQL:"
echo "-- $(quoted_args "${cmd[@]}")"
else
"${cmd[@]}"; r=$?
fi
exit $r
elif [ "$method" == insert ]; then
ac_set_tmpfile inserts
[ -n "$truncate" ] && echo "$truncate" >>"$inserts"
awkcsv <"$input" >>"$inserts" -s "$skip_lines" -v table="$table" \
-a "$__AWK_MYSQLFUNCS"'{
count = length(ORIGHEADERS)
2014-03-03 17:16:27 +04:00
fields = ""
for (i = 1; i <= count; i++) {
if (i > 1) fields = fields ", "
fields = fields "`" ORIGHEADERS[i] "`"
}
2014-03-03 17:08:49 +04:00
values = ""
for (i = 1; i <= count; i++) {
if (i > 1) values = values ", "
values = values format_sqlvalue($i)
}
print "insert into `" table "` (" fields ") values (" values ");"
}
END {
print "commit;"
}'
r=0
if [ -n "$fake" ]; then
cat "$inserts"
else
cat "$inserts" | "$scriptdir/mysqlcsv" "${mysqlargs[@]}" -f -; r=$?
fi
ac_clean "$inserts"
exit $r
elif [ "$method" == update ]; then
array_contains fields "$update_key" || die "$update_key: ce champ n'existe pas dans la source"
ac_set_tmpfile updates
[ -n "$truncate" ] && echo "$truncate" >>"$updates"
awkcsv <"$input" >>"$updates" -s "$skip_lines" \
-v table="$table" -v update_key="$update_key" \
-a "$__AWK_MYSQLFUNCS"'{
set_values = ""
cond = ""
count = length(ORIGHEADERS)
for (i = 1; i <= count; i++) {
field = ORIGHEADERS[i]
value = format_sqlvalue($i)
if (field == update_key) {
2014-03-03 17:16:27 +04:00
cond = "`" field "`=" value
2014-03-03 17:08:49 +04:00
} else {
if (set_values) set_values = set_values ", "
2014-03-03 17:16:27 +04:00
set_values = set_values "`" field "`=" value
2014-03-03 17:08:49 +04:00
}
}
print "update `" table "` set " set_values " where " cond ";"
}
END {
print "commit;"
}'
r=0
if [ -n "$fake" ]; then
cat "$updates"
else
cat "$updates" | "$scriptdir/mysqlcsv" "${mysqlargs[@]}" -f -; r=$?
fi
ac_clean "$updates"
exit $r
2014-02-27 14:35:14 +04:00
else
2014-03-03 17:08:49 +04:00
die "BUG: $method: method non implémentée"
2014-02-27 14:35:14 +04:00
fi