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

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

  1. Tutoriel MySQL en Go [partie 1: installation]
  2. Tutoriel MySQL en Go [partie 2: connexion et configuration]
  3. Tutoriel MySQL en Go [partie 3: transfert de données]
  4. Tutoriel MySQL en Go [partie 4: transactions]