Créer une extension Postgres en Rust, Episode 2 : Demander le luhn

Photo Credit Just Another Photography Dude

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:

Important: Cette série d'articles n'est pas une introduction au langage Rust ! Il existe d'autres ressources pour ça (notamment ici et ). 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 et valid;

  • 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