"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Création de requêtes SQL maintenables avec Golang

Création de requêtes SQL maintenables avec Golang

Publié le 2024-08-22
Parcourir:847

Maintainable SQL Query Building with Golang

Toute application travaillant avec des requêtes SQL peut bénéficier de l'utilisation d'un générateur de requêtes pour améliorer la lisibilité, la maintenabilité et la sécurité du code. En fait, il existe de nombreuses bibliothèques différentes qui font exactement cela dans Golang. Chez Vaunt, nous avons essayé de nombreuses options différentes avant de finalement décider d'en créer une nous-mêmes. En fin de compte, nous voulions quelque chose de sécurisé et fournissant un remplacement de variable pour empêcher l'injection SQL tout en restant lisible et capable d'avoir des instructions conditionnelles. Nous avons donc créé une nouvelle bibliothèque appelée tqla, publiée et annoncée à la fin de l'année dernière. Vous pouvez en savoir plus à ce sujet dans cet article.

Avant de créer tqla, nous utilisions principalement Squirrel pour notre logique de création de requêtes SQL, et nous le recommandons vivement. Nous utilisons toujours Squirrel dans certains domaines, mais avons progressivement commencé à remplacer et à implémenter une nouvelle logique de création de requêtes avec tqla. Nous avons trouvé de nombreux cas dans lesquels tqla a amélioré notre capacité à maintenir notre code et à résoudre les problèmes que nous avons rencontrés lors de l'utilisation d'autres générateurs d'instructions.

Cas d'utilisation réel

Chez Vaunt, nous avons récemment subi une migration de base de données de CockroachDB vers TiDB. Bien que CockroachDB soit performant et fiable, nous avons finalement été confrontés à la décision d'ajouter à notre pile technologique pour prendre en charge une base de données OLAP. Le besoin était de soutenir notre charge de travail analytique sur notre produit open source d’informations sur la communauté. Pour réduire notre empreinte technologique, nous avons décidé d'aller de l'avant avec TiDB et de profiter de l'architecture HTAP de la base de données. 

CockroachDB est largement compatible avec PostgreSQL et nous avons utilisé la syntaxe PostgreSQL pour bon nombre de nos requêtes SQL. Pour passer à TiDB, nous avons dû modifier quelques-unes de nos tables et mettre à jour les requêtes pour utiliser la syntaxe MySQL. À certains endroits au cours de la migration, nous avons constaté que nous n'utilisions pas correctement les instructions de création de requêtes conditionnelles et que nous ne disposions pas des tests appropriés pour détecter que les instructions n'étaient pas générées correctement.

Démonstration

Dans le fichier README de Squirrel, vous trouverez un exemple de la façon dont vous pouvez utiliser la création de requêtes conditionnelles pour mettre à jour les instructions avec des filtres facultatifs :

if len(q) > 0 {
    users = users.Where("name LIKE ?", fmt.Sprint("%", q, "%"))
}

Voici un exemple réel, mais simplifié, de la façon dont nous avons mis à jour l'une de nos requêtes pour joindre des tables de manière conditionnelle et ajouter un filtre facultatif :

psql := squirrel.StatementBuilder.PlaceholderFormat(squirrel.Question)

statementBuilder := psql.Select(`i.id`).
From("vaunt.installations i").
Where(`entity_name = ?`, name)

if len(provider) > 0 {
    statementBuilder.Where(`provider = ?`, provider)
}

if len(repo) > 0 {
    statementBuilder.Join(`repositories as r on JSON_CONTAINS(i.repositories, CONCAT('["', r.id, '"]'))`)
    statementBuilder.Where(`r.name = ?`, repo)
}

Pouvez-vous repérer le problème avec le code ? Si ce n’est pas le cas, ne vous inquiétez pas : c’est quelque chose qui a également échappé à nos propres révisions de code jusqu’à ce que nous exécutions nos tests. 

Le problème ici est que nous avons oublié de mettre à jour le générateur d'instructions avec le résultat des fonctions du générateur. Par exemple, le filtre de condition du fournisseur doit plutôt lire :

if len(provider) > 0 {
    statementBuilder = statementBuilder.Where(`provider = ?`, provider)
}

Il s'agit d'une erreur relativement simple à commettre et peut facilement être détectée avec suffisamment de cas de test, mais comme il ne s'agit pas d'un code techniquement invalide, cela peut prendre un certain temps pour réaliser immédiatement ce qui se passe.

Un autre problème de lisibilité avec cette configuration est que la jointure conditionnelle est séparée de l'instruction select initiale. Nous pourrions réorganiser le constructeur pour placer chaque élément là où il doit aller, mais cela nécessiterait plusieurs vérifications d'instructions conditionnelles en double et souffrirait toujours de certains problèmes de lisibilité.

Utiliser tqla

La démonstration ci-dessus utilisant Squirrel a depuis été réécrite, et l'équivalent en tqla ressemble à ceci :

t, err := tqla.New(tqla.WithPlaceHolder(tqla.Question))
if err != nil {
    return nil, err
}

query, args, err := t.Compile(`
    SELECT i.id
    FROM vaunt.installations as i
    {{ if .Repo }}
    JOIN vaunt.repositories as r on JSON_CONTAINS(i.repositories, CONCAT('["', r.id, '"]'), '$')
    {{ end }}
    WHERE entity_name = {{ .Name}}
    {{ if .Provider }}
    AND i.provider = {{ .Provider }}
    {{ end }}
    {{ if .Repo }}
    AND r.name = {{ .Repo }}
    {{ end }}
    `, data)
if err != nil {
    return nil, err
}

Comme vous pouvez le constater, la syntaxe du modèle pour tqla rend l'incorporation de clauses conditionnelles très simple. Tqla remplace automatiquement les variables que nous définissons par nos espaces réservés spécifiés et fournit les arguments que nous pouvons utiliser avec notre pilote SQL pour exécuter l'instruction.

Semblable à Squirrel, cette approche de création d'instructions est facile à tester, car nous pouvons créer différents ensembles d'objets de données à transmettre au générateur de modèles et valider la sortie.

Vous pouvez voir que nous sommes facilement en mesure d'ajouter des éléments conditionnels de la requête là où ils s'intégreraient le mieux. Par exemple, nous avons ici un JOIN conditionnel directement après l'instruction FROM et bien que nous ayons encore plusieurs vérifications de conditions, cela ne complique pas trop le modèle.

Fonctions personnalisées

Une autre fonctionnalité intéressante de tqla qui contribue à améliorer la maintenabilité de nos constructeurs SQL est la possibilité de définir des fonctions personnalisées que nous pourrions utiliser dans les modèles pour abstraire une certaine logique de transformation.

Voici un exemple de la façon dont nous avons utilisé une fonction pour convertir la valeur time.Time du Golang en sql.NullTime pour nous permettre de faire une insertion avec nos objets de données sans avoir besoin de la convertir au préalable :

funcs := template.FuncMap{
    "time": func(t time.Time) sql.NullTime {
        if t.IsZero() {
            return sql.NullTime{Valid: false}
        }
        return sql.NullTime{Time: t, Valid: true}
    },
}

t, err := tqla.New(tqla.WithPlaceHolder(tqla.Question), tqla.WithFuncMap(funcs))
if err != nil {
    return err
}

Avec cette fonction définie dans notre carte des fonctions tqla, nous pouvons désormais l'utiliser librement dans nos modèles de requête en lui fournissant un paramètre de l'objet de données qui est un champ time.Time. Nous pouvons même appeler cette fonction plusieurs fois dans le même modèle avec des champs différents.

Voici un exemple simplifié :

statement, args, err := t.Compile(`
    INSERT INTO events
        (name, created_at, merged_at, closed_at)
    VALUES ( 
        {{ .Name }},
        {{ time .CreatedAt }},
        {{ time .MergedAt }},
        {{ time .ClosedAt }}
    )`, eventData)

Conclusion

En conclusion, nous pensons que l'utilisation de tqla peut aider à améliorer la maintenabilité de la logique de création de requêtes tout en offrant un utilitaire puissant pour créer des requêtes dynamiques. La simplicité de la structure du modèle permet une lisibilité claire du code et peut accélérer le débogage des erreurs potentielles.

Nous avons créé tqla open source pour partager cette bibliothèque dans l'espoir qu'elle constitue une bonne option pour les autres utilisateurs souhaitant un moyen simple, maintenable et sécurisé de créer des requêtes SQL dans de nombreux types d'applications différents.

Si vous êtes intéressé, veuillez consulter le référentiel et lui donner une étoile si cela vous aide de quelque manière que ce soit. N'hésitez pas à faire des demandes de fonctionnalités ou des rapports de bugs !

Nous sommes toujours ouverts à recevoir des commentaires et des contributions.

Pour rester au courant des développements futurs, suivez-nous sur X ou rejoignez notre Discord !

Déclaration de sortie Cet article est reproduit sur : https://dev.to/vauntdev/maintainable-sql-query-building-with-golang-4kki?1 En cas d'infraction, veuillez contacter [email protected] pour le supprimer.
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3