TP SQLx
Cartouche
| Champ | Valeur |
|---|---|
| Module | Programmation Générale en Rust 202 |
| Auteur·e·s | Élise Philippe |
| Édition | 2025-02-21 |
| Durée | 2~3 séances |
| Taille des équipes | 2~3 personnes |
| Rendu | via git, dépôt $YEAR_sqlx, droits en lecture à delivery_collector |
| Nom du crate | tp_sqlx |
Règlement
Prenez connaissance du règlement ici
L’intégralité de la bibliothèque standard est autorisée. Vous êtes encouragés à vous servir des outils qu’elle vous propose s’ils résolvent un de vos problèmes. Votre objectif c’est d’éviter de ré-inventer la roue quand ça n’est pas nécessaire.
Certains crates sont proposés dans le sujet. Vous avez le droit de vous servir de toutes leurs fonctionnalités.
Vous pouvez rechercher des alternatives à ces crates, voir des crates qui vous proposent des fonctionnalités complètement différentes. Si ces crates affectent l’écriture de votre algorithme ou l’architecture de votre programme, vous devez les faire valider en amont.
Barème
4 points par étapes
Introduction
L’objectif de ce TP : construire le code nécessaire à l’interaction avec une petite base de données de gestion de tâches.
Ses entités seront simples :
- utilisateur user
- nom (exemple : “Martin Louage”)
- login (exemple mlouage)
- date de création
- projet project
- nom
- auteur
- tâches
- date de création
- tâche task
- nom
- description (optionelle)
- auteur
- projet
- échéance
- date de création
Schéma Entity Relationship logique
Étape 1 : préparation BDD
Avant toutes choses il nous faut une base de données. Sur votre ordinateur, déployez le moteur de base de données de votre choix. Vous pouvez utiliser ceci comme base de travail. Il s’agit d’une BDD postgres déployable avec dockeret docker compose. Pour modifier les tables avec lesquelles la base de donnée s’initialise, il suffit de supprimer le fichier mig/01.sql et le remplacer par le votre.
# pour créer et lancer la base de données (en utilisant le contenu de "mig/")
docker compose up -d
# pour arrêter et supprimer la base de données
docker compose down
# pour seulement arrêter la base de données
docker compose stop
Pour valider cette étape :
- [ ] Écrivez les tables et les colones de votre BDD.
- [ ] Écrivez des insertions pour des données d’exemple.
- [ ] Déployez la BDD en local.
Étape 2 : fonctions CRUD
Faire du CRUD pour faire du CRUD ça n’a pas forcément d’intérêt. Mais faire du CRUD pour prendre ses aises avec certains outils, c’est intéressant.
Les fonctions que vous devez faire pour les utilisateurs, les tâches et les projets :
- lister,
- lister par clé étrangère (pour tâches et projets)
- récupérer par ID (et par login pour les utilisateurs),
- modifier,
- supprimer.
Les fonctions doivent prendre en paramètre un impl sqlx::PgExecutor<'e> (ou l’exécuteur qui correspond à votre moteur de BDD).
Un exemple de fonction de requête préparée :
async fn get_user_1<'e>(
executor: impl sqlx::PgExecutor<'e>,
) -> Result<model::UserRow, sqlx::Error> {
let mut query = sqlx::query_as(r#"SELECT * FROM "user" WHERE id = $1;"#);
query = query.bind(1);
let row: model::User = query.fetch_one(executor).await?;
println!("prepared request example {row:#?}");
Ok(row)
}
Exemple d’appel de cette fonction :
// example call
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.connect("postgres://legolas:example@localhost/legolas")
.await?;
let user = get_user_1(&pool).await?;
dbg!(user);
Vous devez aussi écrire les structures utilisées par ces fonctions (en valeur de retour et en argument).
Voici un exemple de structure utilisateur avec des colones id, login, name et created_on que pourrait renvoyer les fonctions pour lister et récupérer un utilisateur :
#[derive(Debug, sqlx::FromRow)]
pub struct UserRow {
pub id: i32,
pub login: String,
pub name: String,
pub created_on: chrono::DateTime<chrono::Local>, // created on timestamp
}
Étape 3 : jointure du nom de l’auteur
Les projets et les tâches ont un auteur dont l’identifiant est utilisé en clé étrangère. Pour faciliter la lecture par un humain, ajoutez aux fonctions qui récupèrent et listent les projets et les tâches, une jointure sur utilisateurs pour SELECT le login de l’auteur. (Il faut ajouter author_login aux structures concernées)
Étape 4 : requêtes triées
Écrivez une fonction pour lister les tâches en les triant sur un champ choisi en argument. Pour la selection du champ à trier, passez par une énumération. Pour la direction du tri, passez par une énumération.
Vous allez devoir générer la requête en concaténant des chaînes de caractères. Gardez en tête que les macros query! et query_as! ne fonctionnent que sur des chaînes 'static, donc pas sur des chaînes créées dynamiquement, à l’exécution.
Étape 5 : listes paginées
Quand on travaille avec une faible quantité de données, aucun problème à lister l’intégralité d’une table. Dans la vrai vie, on va limiter les données renvoyées lorsqu’on nous demande une liste. L’approche ancestrale c’est la limite LIMIT et le saut SKIP. Dont le principal défaut c’est que si un élément est inséré pendant que l’on passe de page en page, le contenu des pages sera décalé.
Il existe une solution à ce problème, qui fonctionne sur des données triées. C’est de noter la valeur de la colonne triée du dernier élément ; puis à la requête suivante, demander les éléments après cette valeur.
Exemple :
- une requête triée par date de création descendante vous renvoie 10 éléments, dont le dernier élément qui a la date du 2025-02-20 16:27 ;
- pour la prochaine page il suffit de faire la même requête mais avec pour filtre que la date de création doit être strictement inférieure à 2025-02-20 16:27.
Cette méthode là a aussi un avantage de performance lorsque la colonne utilisée est indexée.
Écrivez une fonction qui permet cette pagination, dont on peut argumenter l’appel avec :
- la colonne à trier en énumération ;
- la direction de tri en énumération ;
- une option pour la dernière valeur obtenue lors d’un potentiel appel précédent.