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 â€ïž
