Créer une extension Postgres en Rust, Episode 2 : Demander le luhn
Préambule
Cet article fait partie d'une série consacrée au framework PGRX, un environnement de développement qui facilite la conception d'extensions PostgreSQL avec le langage Rust.
Retrouvez les autres articles de la série ci-dessous:
- Episode 1 : Un nouveau monde
- Episode 2 : Demander le luhn
- Episode 3 : Le chant du SIREN
- Episode 4 : Accrocher le parapet
Important: Cette série d'articles n'est pas une introduction au langage Rust ! Il existe d'autres ressources pour ça (notamment ici et là). On se focalisera ici sur la mécanique interne d'une extension Postgres.
Chaque épisode est conçu en 2 parties : un tutoriel de 30 minutes environ, suivi d'exercices pour aller plus loin. Les exercices sont optionnels.
Objectifs
Dans cette seconde partie, nous avons pour objectif de:
- Utiliser une librairie Rust (
crate
) externe - Lancer une batterie de tests unitaires
Contexte
L'exemple proposé dans l'épisode précédent (cf. [Episode 1: Bonjour le monde]) est intéressant mais on aurait obtenir le même résultat avec une extension en SQL pure ou en pl/pgsql. Bref rien qui ne justifie en soit l'usage d'un langage compilé comme Rust.
Cette fois, l'exemple va consiste à implémenter la formule de Luhn qui est un mécanisme de somme de controle utilisé pour valider une variété de numéros de comptes, comme les numéros de cartes bancaires, les numéros d'assurance sociale canadiens, etc.
Démarrer
Si vous n'avez pas encore installer PGRX, reportez-vous à la section "Démarrage" de l'article "Episode 1 : Un nouveau monde".
ou sinon utilisez l'image docker:
docker run -it --volume `pwd`:/pgrx daamien/pgrx
Créer l'extension
On peut maintenant créer une extension nommée pg_luhn
avec:
cargo pgrx new pg_luhn
cd pg_luhn
Ajouter une dépendance externe
La communauté Rust
propose un registre de paquets (nommés crates
) via la plateforme
https://crates.io/.
Une recherche rapide montre qu'il existe déjà plusieurs implémentations de la
formule de Luhn. On utilisera ici le paquet luhn3
:
https://crates.io/crates/luhn3
Pour cela, on lance la commande:
cargo add luhn3
Le fichier Cargo.toml
contient désormais le nom et la version du paquet dans
la section dépendances comme ceci:
[dependencies]
luhn3 = "1.1.0"
pgrx = "=0.11.0"
Les numéros de versions peuvent varier suivant la date où vous réaliser ce tutoriel.
Encapsuler les fonctions externes
On peut désormais ouvrir le fichier src/lib.rs
et appeler directement la
fonction checksum
de la librairie et la rendre accessible dans Postgres :
#[pg_extern]
fn luhn_checksum(input: &str) -> char {
luhn3::decimal::checksum(input.as_bytes()).expect("Input should be decimal") as char;
}
Lancer l'extension
On peut ensuite directement tester l'extension
cargo pgrx run
Attention! Cette commande va compiler une centaine de paquets Rust (
crates
) et peut durer une dizaine de minutes !
CREATE EXTENSION pg_luhn;
SELECT luhn_checksum('1');
luhn_checksum
---------------
8
(1 row)
SELECT luhn_checksum('A');
ERROR: Input should be decimal
Ecrire une batterie de test
PGRX permet d'intégrer les tests unitaires directement dans
le fichier src/lib.rs
au plus près du code. Ici on vérifie
simplement que la valeur de retour de la fonction
luhn_checksum
grace à la macro assert_eq!
#[cfg(any(test, feature = "pg_test"))]
#[pg_schema]
mod tests {
use pgrx::prelude::*;
#[pg_test]
fn test_luhn_checksum() {
assert_eq!("8", crate::luhn_checksum("1"));
}
}
Une fois le test ajouté dans src/lib.rs
, on peut lancer les tests:
cargo pgrx test
[...]
test tests::pg_test_luhn_checksum ... ok
test tests::pg_test_hello_pg_luhn ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out;
finished in 5.26s
Par défaut, les tests sont lancés sur une instance PostgreSQL 13.
Pour tester sur une autre version majeure, il suffit de préciser le numéro:
cargo pgrx test pg12
Pour aller plus loin...
Voici quelques propositions d'activités pour poursuivre la découverte:
-
Ecrire une fonction
append(TEXT)
, qui concatène une valeur et sa somme de controle. -
En utilisant la fonction luhn3::decimal::valid, créer une fonction
valid(TEXT)
qui vérifie si une valeur respecte la formule de Luhn. -
Ecrire plusieurs tests pour valider le fonctionnement des fonctions
append
etvalid
; -
Modifier le fichier
Cargo.toml
, pour lancer les tests sur PostgreSQL 16 par défaut.
Un exemple d'implémentation est disponible sur:
https://gitlab.com/daamien/pgrx-notebook/-/blob/main/pg_luhn/src/lib.rs
Ecrire la même chose en SQL ?
En guise de comparaison, voici un implémentation équivalente en SQL pur:
https://wiki.postgresql.org/wiki/Luhn_algorithm
Bilan
Nous venons de voir à quel point qu'il est pratique de pouvoir s'appuyer sur l'écosystème de paquets Rust, ce qui peut largement simplifier l'écriture d'une extension.
Par ailleurs, l'extension est compilée avec toutes ses dépendances ce qui fait que le binaire produit n'a pas de dépendances externes.
Enfin le système de tests unitaires intégré dans PGRX est à la fois simple et puissant puisqu'il permet de lancer les tests sur toutes les versions majeures majeures Postgres. Comparé au système de test de [PGXC] c'est un gain énorme.
La suite au prochain épisode !
TBD