Partie 1 : Construire le REPL
Lecture 7 min. âą
Table des matiĂšres
- Cahier des charges
- Modélisation
- Parsing
- Evaluation et affichage
- Boucle dâĂ©coute
- Vérification de notre REPL
- Conclusion
Les articles de la série
- Partie 1 : Construire le REPL
- Partie 2 : Sérialisation des données
- Partie 3 : Database
- Partie 4 : Tables
- Partie 5 : Parser les commandes, création de la boßte à outils
- Partie 6 : Parser les commandes SQL
- Partie 7 : Tuples de données
- Partie 8 : Contraindre les données via un schéma
- Partie 9 : Découper le stockage des tables en pages
- Partie 10 : Clefs primaires
- Partie 11 : Expressions Logiques SQL
- Partie 12 : Scans et filtrage
- Partie 13 : UTF-8 et caractÚres échappés
- Partie 14 : Attributs nullifiables
- Partie 15 : Index secondaires
- Partie 16 : Directive EXPLAIN
- Partie 17 : Logical Plan
- Partie 18 : Exécution du plan logique
- Partie 19 : Suppression d'enregistrements
- Partie 20 : Réindexation et compactation de tables
Bonjour Ă toutes et Ă tous đ
Mon mĂ©tier de tous les jours est de fabriquer des bases de donnĂ©es, mais en vrai je ne sais pas vraiment comment ça marche. đ
Au dĂ©tours, dâune visite sur twitter je suis tombĂ© sur une mine dâor. Une personne qui a documentĂ© son voyage en C. đ€©
Mais moi je ne suis pas un âgars du pointeurâ, je prĂ©fĂšre les rĂ©fĂ©rences et lâabsence de Undefined Behavior, bref je fais du Rust! đŠ Je vous ai dĂ©jĂ parlĂ© de Rust ..? đ€Ł
Pour cette sĂ©rie dâarticle car oui ça sera une sĂ©rie, sinon je vais vous noyer. Nous allons rĂ©implĂ©meter sqlite, la base de donnĂ©es relationnelle, en utilisant uniquement la lib standard et en limitant au strict minimum lâusage de lâunsafe.
Exit donc lâusage de la lib serde, nous allons devoir nous dĂ©brouiller sans ^^â
La premiĂšre chose que nous allons rĂ©aliser est la crĂ©ation de lâinvite de commande ou Read Eval Print Loop.
Ce qui signifie Boucle de lecture et dâĂ©valuation et dâaffichage. En dâautres terme, le mĂ©canisme qui permettre Ă un utilisateur dâentrer une commande qui va ĂȘtre parsĂ©e puis exĂ©cutĂ© par la base de donnĂ©es.
Cahier des charges
Il est toujours bon, dâavoir un cap Ă suivre pour Ă©viter lâover-engineering ou au contraire de manquer complĂštement sa cible.
Nous allons dire que notre programme renvoie à chaque commande exécutée et au démarrage un invite de commandes
db > commande utilisateur
retour de la DB
peut-ĂȘtre multiligne
db >
Pour commencer, nous allons simplifier drastiquement le probÚme en définissant un set trÚs réduit de commandes
.exit
: permet de quitter lâinvite de commande et de fermer le programmeinsert 1 name email
: insert un utilisateur dâID 1 et de nom ânameâ et dâemail âemailâselect
: renvoie toutes les entrées stockées
Cela nâa lâair de rien mais juste cela demande de rĂ©flĂ©chir un peu. đ
Nomemclature
Nous allons décomposer nos commandes en deux lots:
- les mĂ©ta-commandes qui nâintĂ©ragissent pas avec la base de donnĂ©es Ă proprement parler
- les commandes qui manipulent de la données
Les mĂ©ta-commandes commencent toutes par un â.â. Ceci est un critĂšre qui peut ĂȘtre pris en compte plus tard.
Modélisation
LâexpressivitĂ© de Rust est formidable outils de modĂ©lisation. ModĂ©lisons donc notre set de commandes supportĂ©es.
Les mĂ©ta-commandes pour commencer, on ne possĂšde quâune seule commande.
Les commandes peuvent possĂ©der des paramĂštres. âInsertâ en possĂšde, âSelectâ non.
Note
La modélisation est naïve et restrictive pour ce premier jet. Je préfÚre itérer sur du simple que de devoir gérer la complexité tout de suite.
Nous allons en faire lâunion via une troisiĂšme enumĂ©ration. On rajoute une lifetime 'a
pour nous Ă©pargner une copie en cas dâerreur.
Lâutilisateur va utiliser notre REPL pour entrer des commandes. Ce qui signifie que nous allons traiter des chaĂźne de caractĂšres.
En informatique, lâanalyse et la transformation dâune chaĂźne de caractĂšres en des donnĂ©es manipulable se nomme un parsing.
Nous allons également modéliser ce parse.
Nous pouvons nous aider du TDD pour nous guider dans sa construction.
On peut définir une méthode parse
pour faire ce que lâon dĂ©sire.
Et lui associer des tests.
On se retrouve à devoir gérer trop de choses à parser. On va donc diviser pour mieux régner, et pour ce faire nous allons créer un nouveau trait.
Le but de ce trait est de permettre dâessayer de transformer une chaĂźne de caractĂšres en quelque chose qui nous intĂ©resse. On se mĂ©nage aussi la possibilitĂ© dâĂ©chouer de deux maniĂšre: soit on ne trouve pas dâalternative possible et on renvoie un None dâoĂč lâOption
, soit une alternative a Ă©tĂ© trouvĂ© mais câest un Ă©chec.
Nous allons modéliser également cette échec par une énumération.
On lui adjoint le nécessaire pour en faire une Error
.
Parsing
Notre modĂ©lisation est complĂšte, nous allons pouvoir passer Ă lâimplĂ©mentation.
Méta-commandes
Nous nâavons quâune mĂ©ta-commande: â.exitâ.
Ainsi nous avons le set de tests suivant:
Et en dĂ©finir lâimplĂ©mentation.
Commandes SQL
Deux commandes sont à implémenter:
insert 1 name email
: insert un utilisateur dâID 1 et de nom ânameâ et dâemail âemailâselect
: renvoie toutes les entrées stockées
La commande dâinsert est la plus demandante en terme de contrĂŽle des donnĂ©es.
Voici un set de tests qui couvrent
les test de la commande âselectâ sont plus restreints:
Je vous propose cette implémentation qui répond au deux sets de tests.
Note
Ceci est une approche naĂŻve du parse, elle ne sera pas notre implĂ©mentation finale, car elle a plein de problĂšmes dont lâincapacitĂ© de permettre de mettre des espaces dans les champs par exemple.
Nous rĂšglerons les soucis progressivement.
Commandes
On peut alors tout rassembler.
Etant donné que nous avons introduit de la fallibilité dans le parse de la commande SQL.
Nous devons la reflĂ©ter Ă©galement dans nos tests. Je ne vĂ©rifie que les cas voulus, les cas dâerreurs Ă©tant gĂ©rĂ© par les tests des commandes spĂ©cifiques.
On peut alors se faire une implémentation de notre méthode de parse
revue et corrigée.
Le fait dâavoir implĂ©menter un trait permet de normaliser tous les comportements et dâĂ©crire du code simple Ă lire et comprendre.
Evaluation et affichage
On a fait le R de REPL. Nous savons lire et interpréter la commande, mais il nous reste encore du chemin.
Notre prochaine Ă©tape est dâen faire quelque de cette commande.
Nous allons faire trĂšs simple dans un premier temps.
La commande â.exitâ va fermer avec succĂšs le programme.
Les commandes âselectâ et âinsertâ afficher un petit message dans la console.
Nous avons la chance dâĂȘtre en Rust, donc continuons Ă modĂ©liser correctement les choses.
Nous pouvons dĂ©clarer un trait qui se chargera de gĂ©rer lâexĂ©cution de la commande.
On crĂ©er notre erreur en avance Ă©galement. Notre exĂ©cution pourra Ă©chouer dans lâavenir.
Et maintenant on peut passer Ă lâimplĂ©mentation du trait.
On commence par les méta-commandes.
Puis les commandes SQL
Et finalement notre Command, qui rĂ©alise le dispatch explicite de lâĂ©xĂ©cution.
On ne fera pas de TDD ici, car ça sây prĂȘte difficilement, il faudrait tordre le code pour y arriver, et ce nâest pas souhaitable.
Au lieu de ça, nous allons continuer la construction du REPL.
Boucle dâĂ©coute
Si on ne veux pas que notre REPL se coupe dĂšs la premiĂšre commande, il faut en faire une boucle.
Etant donné que toute nos erreurs sont compatible avec le trait Error
, il est aisĂ© de renvoyer lâerreur sans la dĂ©finir explicitement.
Cette fonction run conclue le L de notre REPL.
Vérification de notre REPL
On se créé une méthode main
Et cargo run
!
db > select
Ici sera la future commande select
db > insert 1 name email@domain.tld
Ici sera la future commande insert
db > insert
Error NotEnoughArguments
db > insert one name email@domain.tld
Error ExpectingInteger
db > select toto
Error TooManyArguments
db > command unknown
Command not found: command unknown
db > .exit
Nous avons le rĂ©sultat escomptĂ© đ
Conclusion
Ceci conclu notre premiÚre partie. Nous nous sommes largement reposé sur le systÚme de type de Rust pour concevoir notre modélisation et nous allons continuer.
Comme vous pouvez le voir, la librairie standard de Rust permet de faire beaucoup de choses sans avoir Ă tirer des libs externes.
Nous allons continuer dans cette voie.
Dans la prochaine partie nous allons aborder le sujets de la sérialisation de la données que nous allons insérer en base de données.
Vous pouvez trouver ci-joint le lien vers la branche du repo git contenant le code de cette partie.
Merci de votre lecture et Ă la prochaine pour sĂ©rialiser de la data. đ€©