Partie 7 : Tuples de données
Lecture 5 min. âą
Table des matiĂšres
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 đ
Depuis la [partie 6](/rustqlite-6 nous sommes capables de parser une commande une commande permettant de crĂ©er une table avec un schĂ©ma arbitraire, puis dây insĂ©rer et finalement dây rĂ©cupĂ©rer des records.
Aujourdâhui nous allons gĂ©nĂ©raliser les entitĂ©s User
et Car
que nous avions utilisĂ©s comme placeholder pour simuler les opĂ©rations dâenregistrements et de lecture sans devoir se soucier des problĂ©matique de schĂ©mas.
Nous allons toujours pas nous occuper des schĂ©mas, mais par contre nous allons introduire le concept fondamental qui permettra de stocker de maniĂšre optimale des enregistrement et nous donnera de maniĂšre quasi gratuite la lâatomicitĂ© des update de colonnes.
Mais le chemin est encore long. đ
On va commencer par déjà généraliser nos données stockées.
Tuples
Rappelez vous la commande dâinsertion est rĂ©duite en une InsertIntoCommand
, qui possĂšde une map de Value
Cette Value
se dĂ©compe elle-mĂȘme en deux variantes.
La question est alors:
Question
Comment peut-on stocker cette énumération en base de données?
La rĂ©ponse ne va pas vous dĂ©friser, il faut sĂ©rialiser, tout comme lâon avait fait avec User
et Car
.
Comme câest une Ă©numĂ©ration, il faut quâĂ la dĂ©sĂ©rialisation on puisse recrĂ©er la bonne variante.
Pour cela on se créé une autre énumération qui va servir de discriminant à la désérialisation.
On rajoute une erreur de désérialisation supplémentaire.
Maintenant on peut implémenter la sérialisation.
Et son complémentaire de désérialisation.
Nous sommes désormais capables de sérialiser nos Value
.
Mais notre commande dâinsertion comporte plusieurs valeurs. Ce nâest donc pas une Value
mais un Vec<Value>
.
Alors on pourrait implémenter Serializable
sur Vec<Value>
, mais on va se donner le luxe dâutiliser les outils de Rust et utiliser la blanket implementation.
On a exactement le mĂȘme concept que pour lâĂ©numĂ©ration, Ă la dĂ©sĂ©rialisation, il faut quâon soit capable de dĂ©terminer combien dâĂ©lements sont constitutifs du Vec
.
La dĂ©sĂ©rialisation nâest pas plus complexe.
Et cette fois-ci on est bon ! đ
Si on résume notre sérialisation, on se retrouve pour un [Integer(42), Text("test")]
avec ceci en mémoire.
0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xd6 0x01 0x04 0x74 0x65 0x73 0x74
^ ^ ^ ^ ^ ^
taille D=Int 42 sur 8 bytes encodé en litte-endian D=Text taille "test" encodé en UTF-8
du Vec String
Cette structure de données sérialisée est notre Tuple.
Note
Le format va radicalement changer lorsque lâon introduira le schĂ©ma dans la sĂ©rialisation. Pour le moment nos donnĂ©es sont auto-porteuses du schĂ©ma, mais on gĂąche des bytes Ă encoder des donnĂ©es pas forcĂ©ment utile comme les tailles de vecteur, les tailles de string et les discriminants.
Sur des millions dâenregistrements, cela peut avoir un poids considĂ©rable !!
Nos entiers également prennent vraiment trop de place, on va également revoir leur stockage.
Intrduction du nouveau parser
Maintenant que lâon a notre tuple de donnĂ©es.
Modification de Table
MĂȘme si pour le moment nous nâallons pas rĂ©ellement lâutiliser, nous allons prĂ©parer le terrain pour les futurs travaux.
Nous allons doter la table dâun SchĂ©ma
, directement issu de la commande CreateTableCommand
.
Et on nâoublie de modifier le constructeur en consĂ©quence.
Modification de Database
PrĂ©dĂ©mment pour identifier les tables dans la Database on se servait de lâĂ©numĂ©ration TableName
.
Or celle-ci nâa plus de sens dĂ©sormais car lâutilisateur Ă la crĂ©ation de la table la nomme comme il lâentend.
De mĂȘme la notion de Record
est complÚtement caduque et remplacée par le tuple Vec<Value>
.
On modifie donc Database
pour mapper non pas TableName
mais une String
Ă nos Table
.
Cela a pour incidence de modifier les signature des fonctions en dessous.
Utilisation du nouveau parser
Pour rappel nous avons lâĂ©numĂ©ration Command
comme suit
Ce Command
est visitable. On peut donc en faire un parser en 2 lignes.
On introduit une nouvelle CommandError
On peut alors remplacer notre parser approximatif par quelque chose de bien plus puissant.
Implémentation des commandes
Il nous reste alors dâimplĂ©menter le trait Execute
pour les différente commandes.
Dâabord le CREATE TABLE
.
Puis le INSERT INTO
Finalement SELECT FROM
Tout ceci permettant de faire remonter lâexĂ©cution jusqâĂ la commande
Nâayant pas modifiĂ© lâinterface publique de notre API, nous avons dĂ©jĂ quelque chose de fonctionnel ! đ
Petit tests
Ce que lâon pouvait faire avant, on peut toujours le faire.
db > CREATE TABLE Users (id INTEGER, name TEXT(50), email TEXT(128));
db > INSERT INTO Users(id, name, email) VALUES (1, 'John Doe', 'john.doe@example.com');
db > INSERT INTO Users(id, name, email) VALUES (2, 'Jane Doe', 'jane.doe@example.com');
db > SELECT * FROM Users;
[Text("john.doe@example.com"), Integer(1), Text("John Doe")]
[Text("Jane Doe"), Integer(2), Text("jane.doe@example.com")]
Note
On voit que le tuple nâest pas dans le bon sens car le
HashMap.values().collect()
ne conserve pas lâordre dâinsertion. On remĂ©diera au problĂšme Ă lâintroduction du schĂ©ma.
Et on peut désormais créer des tables arbitrairement nommées et avec un nombre et des types de champs eux aussi arbitraires.
db > CREATE TABLE Birds (name TEXT(50), specie TEXT(128));
db > INSERT INTO Birds(name, specie) VALUES ('titi', 'canary');
db > INSERT INTO Birds(name, specie) VALUES ('Iago', 'parrot');
db > SELECT * FROM Birds;
[Text("canary"), Text("titi")]
[Text("Iago"), Text("parrot")]
db >
On est pas mal quand mĂȘme non ? đ€©
Conclusion
Notre implĂ©mentation du tuple est approximative, mais donne une bonne idĂ©e de lâAPI finale.
Dans la prochaine partie on mettra en place ce schéma tant désiré !
Merci de votre lecture â€ïž