Tutoriel MySQL en Go [partie 3: transfert de données]
Photo par Tobias Fischer sur Unsplash
Rappel :
Ce petit tutoriel vous accompagne sur la mise en place d’un serveur MySQL/MariaDB et sa programmation simple en Go. L’idée c’est de présenter ici le minimum pour avoir une application fonctionnelle. Il s’inscrit dans une petite série d’articles que je voulais faire sur les bases de données.
Sommaire
- Installer votre serveur MySQL, cliquez ici.
- Ouvrir une connexion vers votre serveur MySQL, cliquez ici.
- Transfert de données MySQL avec Go, cliquez ici.
- Transactions dans MySQL avec Go, prochainement
Transfert de données MySQL avec Go
Maintenant, on va pouvoir effectuer des requêtes pour énumérer les entrées dans la table :
rows, err := db.Query(` SELECT id, data, creation FROM ` + tableName) if err != nil { log.Fatalf("SELECT failed: %v\n", err) } defer rows.Close()
Simple non ? Mais alors comment fait-on pour récupérer les données ? Pour ça, on utilise la valeur retournée par la fonction db.Query() :
for rows.Next() { var ( id uint64 data string creation time.Time ) err = rows.Scan(&id, &data, &creation) if err != nil { log.Fatalf("rows.Scan failed: %v\n", err) } fmt.Printf("%-5d | %-32s | %v\n", id, data, creation) }
Comme vous pouvez le voir, on parcourt chacune des lignes et grâce à la fonction rows.Scan() on récupère les variables. Cette fonction se charge d’effectuer la conversion, en prenant en compte le type de la variable passée en paramètre. Cool non ?
Ceci dit, ce bout de code, quoi que simple, ne permet pas de voir une petite difficulté : que se passe-t-il si une colonne est nulle ? Eh bien, on obtient l’erreur suivante :
rows.Scan failed: sql: Scan error on column index 1, name "data": unsupported Scan, storing driver.Value type <nil> int o type *string
Ce message d’erreur est assez explicite, vous ne trouvez-pas ? Pour traiter ce genre de cas, il y a donc deux solutions : soit créer une table dans laquelle les colonnes concernées ne peuvent pas être nulles, ou bien modifier notre code de la façon suivante :
for rows.Next() { var ( id uint64 dataPtr *string creationPtr *time.Time ) err = rows.Scan(&id, &dataPtr, &creationPtr) if err != nil { log.Fatalf("rows.Scan failed: %v\n", err) } data := "<nil>" if dataPtr != nil { data = *dataPtr } fmt.Printf("%-5d | %-32s | %v\n", id, data, creationPtr) }
Ainsi, en faisant de dataPtr un pointeur vers une string, on permet que la colonne correspondante puisse retourner un NULL (ou nil en Go).
Bon maintenant, il ne nous reste qu’à insérer des données dans la table :
res, err := db.Exec(`INSERT INTO `+tableName+` SET data=?, creation=NOW()`, "Hello World") if err != nil { log.Fatalf("db.Exec(INSERT) failed: %v", err) }
Vous avez noté le point d’interrogation dans la requête ? C’est un “placeholder”. On peut passer en paramètre à la fonction db.Exec() (comme à la fonction db.Query()). C’est pratique parce que c’est le driver qui se charge de faire l’escaping de la valeur. Le souci, c’est que ces paramètres dépendent de l’ordre dans lesquels on trouve les points d’interrogation. Du coup, un simple changement dans la requête peut mener à des erreurs difficiles à identifier.
Ce qui serait vraiment bien, c’est de pouvoir utiliser des paramètres nommés comme ça :
res, err := db.Exec(`INSERT INTO `+tableName+` SET data=@data, creation=NOW()`, sql.Named("data", "Hello World")) if err != nil { log.Fatalf("db.Exec(INSERT) failed: %v", err) }
Eh bien le package database/sql le permet. Le problème c’est que ça donne l’erreur suivante :
mysql: driver does not support the use of Named Parameters
Pas cool. Je n’ai pas trouvé d’autre driver qui permette de traiter cela. Dommage. Il semble que cela fonctionne avec le driver pq pour PostgreSQL. Moi qui me posais la question concernant le choix de la base de données pour certains de mes projets …
Reste à savoir quel ID a été créé pour cette nouvelle entrée que nous venons d’ajouter :
lastID, err := res.LastInsertId() if err != nil { log.Fatalf("res.LastInsertId failed: %v\n", err) } fmt.Printf("inserted %d\n", lastID)
Enfin, il est possible de savoir combien de lignes on été affectées par une requête SQL :
rowCount, err := res.RowsAffected() if err != nil { log.Fatalf("res.RowsAffected failed: %v", err) }
Dans le prochain article, nous verrons comment effectuer des requêtes transactionnelles.
Tutoriel MySQL en Go
- Tutoriel MySQL en Go [partie 1: installation]
- Tutoriel MySQL en Go [partie 2: connexion et configuration]
- Tutoriel MySQL en Go [partie 3: transfert de données]
- Tutoriel MySQL en Go [partie 4: transactions]