286 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			286 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/bin/bash
 | |
| # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
 | |
| 
 | |
| 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
 | |
| sont effectuées par MySQL:
 | |
| 
 | |
|     \\0 --> <caractère NUL>
 | |
|     \\b --> <backspace>
 | |
|     \\n --> <newline>
 | |
|     \\r --> <carriage return>
 | |
|     \\t --> <tab>
 | |
|     \\Z --> Ctrl+Z
 | |
|     \\N --> NULL
 | |
| 
 | |
| OPTIONS
 | |
|     -h, --host host
 | |
|     -P, --port port
 | |
|     -u, --user user
 | |
|     -ppassword
 | |
|         Informations de connexion à la base de données
 | |
|     -C, --config CONFIG
 | |
|         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.
 | |
|     -f, --input INPUT
 | |
|         Fichier en entrée. Ne pas spécifier cette option ou utiliser '-' pour
 | |
|         lire depuis l'entrée standard.
 | |
|     -s, --skip-lines NBLINES
 | |
|         Nombre de lignes à sauter dans le fichier en entrée
 | |
|     -n, --fake
 | |
|         Ne pas effectuer l'opération. Afficher simplement la commande SQL.
 | |
|     -T, --truncate
 | |
|         Vider la table avant d'effectuer le chargement
 | |
|     -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
 | |
|         Au lieu de charger de nouvelles données, essayer de mettre à jour la
 | |
|         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é."
 | |
| }
 | |
| 
 | |
| source "$(dirname "$0")/ulib/ulib" || exit 1; urequire DEFAULTS awk
 | |
| 
 | |
| __AWK_MYSQLFUNCS='
 | |
| function format_sqlvalue(value) {
 | |
|   if (value ~ /^[0-9]+$/) {
 | |
|   } 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
 | |
| }
 | |
| '
 | |
| 
 | |
| host=
 | |
| port=
 | |
| user=
 | |
| password=--NOT-SET--
 | |
| configs=()
 | |
| profile=
 | |
| input=
 | |
| skip_lines=
 | |
| truncate=
 | |
| fake=
 | |
| method=load
 | |
| update_key=
 | |
| 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= \
 | |
|     -s:,--skip-lines: skip_lines= \
 | |
|     -n,--fake fake=1 \
 | |
|     -T,--truncate truncate=1 \
 | |
|     -L,--load-data method=load \
 | |
|     -I,--insert-data method=insert \
 | |
|     -U:,-k:,--update-data: '$method=update; set@ update_key' \
 | |
|     @ args -- "$@" && set -- "${args[@]}" || die "$args"
 | |
| 
 | |
| dbtable="$1"; shift
 | |
| fields=()
 | |
| while [ "$#" -gt 0 -a "$1" != "--" ]; do
 | |
|     fields=("${fields[@]}" "$1")
 | |
|     shift
 | |
| done
 | |
| [ "$1" == "--" ] && shift
 | |
| 
 | |
| splitname "$dbtable" database table
 | |
| if [ -z "$table" ]; then
 | |
|     table="$database"
 | |
|     database=
 | |
| 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"
 | |
| 
 | |
| if [ -n "$input" -a "$input" != "-" ]; then
 | |
|     [ -f "$input" ] || die "$input: fichier introuvable"
 | |
| fi
 | |
| 
 | |
| 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"}
 | |
|     ${user:+-u "$user"} ${database:+-D "$database"}
 | |
| )
 | |
| [ "$password" != "--NOT-SET--" ] && mysqlargs=("${mysqlargs[@]}" -p"$password")
 | |
| 
 | |
| [ -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)
 | |
|   fields = ""
 | |
|   for (i = 1; i <= count; i++) {
 | |
|     if (i > 1) fields = fields ", "
 | |
|     fields = fields "`" ORIGHEADERS[i] "`"
 | |
|   }
 | |
|   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) {
 | |
|       cond = "`" field "`=" value
 | |
|     } else {
 | |
|       if (set_values) set_values = set_values ", "
 | |
|       set_values = set_values "`" field "`=" value
 | |
|     }
 | |
|   }
 | |
|   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
 | |
| 
 | |
| else
 | |
|     die "BUG: $method: method non implémentée"
 | |
| fi
 |