Partie 10 : Clefs primaires
Lecture 57 min. âą
Table des matiĂšres
- Modifiation du parser
- Indexation des entrées
- Query Engine
- Utilisation du Query Engine
- Testons !!
- 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 đ
Pour le moment notre seule façon de rĂ©cupĂ©rer de la donnĂ©e depuis notre table consiste Ă rĂ©aliser un full-scan, ce qui est veut dire partir du premier byte de la table et dĂ©sĂ©sĂ©rialiser jusquâĂ la fin.
Autant dire que ce nâest pas vraiment optimale si on cherche un tuple en milieu de table. đ
Tout comme Ă lâĂ©poque du tĂ©lĂ©phone Ă fil, il y avait un gros bouquin qui rĂ©fĂ©rençait le nom de la personne par rapport Ă son numĂ©ro. Nous allons faire de mĂȘme avec nos tuples.
La question maintenant est de savoir dans notre cas qui est le ânomâ et quâest ce que le ânumĂ©roâ de notre âannuaireâ Ă nous.
Il va nous falloir plus dâoutils pour rĂ©pondre Ă ces questions.
Modifiation du parser
La premiĂšre chose que lâon va rajouter câest des nouvelles capacitĂ© Ă notre parser.
Nouvelle grammaire
Pour pouvoir dĂ©finir qui sera le ânomâ de notre annuaire, nous allons dĂ©finir un flag sur un des champs du schĂ©ma de la table Ă la crĂ©ation de celle-ci.
Ce flag va se nommer PRIMARY KEY
, un champ disposant de cette contrainte se nomme la âclef primaireâ de la table, autrement dit la maniĂšre la plus simple de retrouver de la donnĂ©es dans une table.
Voici la nouvelle grammaire du parser pour la commande CREATE TABLE
.CREATE TABLE avec PRIMARY KEY
CreateTable ::= 'CREATE' 'TABLE' LiteralString OpenParen ColumnDef* CloseParen Semicolon
ColumnDef ::= LiteralString ColumnType | LiteralString ColumnType Comma
ColumnType ::= 'INTEGER' Constraint* | ColumTypeText Constraint*
ColumTypeText ::= 'TEXT' OpenParen LiteralInteger CloseParen
LiteralString ::= *
LiteralInteger ::= +
Constraint ::= 'PRIMARY' 'KEY' | 'NOT' 'NULL'
OpenParen ::= '('
CloseParen ::= ')'
Comma ::= ','
Semicolon ::= ';'
Les colonnes disposent dĂ©sormais dâun nouveau composant Constraint
qui va ĂȘtre soit lâenchaĂźnement des tokens PRIMARY KEY
soit NOT NULL
.
Il est possible de dâavoir Ă la fois PRIMARY KEY
et NOT NULL
sur une mĂȘme colonne.
La deuxiĂšme modification va ĂȘtre sur la commande SELECT
.
Pour le moment, on rĂ©alise uniquement ce que lâon nomme des âfull-scanâ, câest Ă dire que lâon part de lâindex 0 de nos tuples, dĂ©marrant Ă la page 0 de notre table, et on itĂšre jusquâĂ ne plus avoir dâentrĂ©e Ă retourner.
Cela est du à la pauvreté de notre commande SELECT
qui ne permet pas de nommer ce que lâon dĂ©sire. Il nous manque littĂ©ralement des âmotsâ pour nous exprimer! đ
Nous allons les rajouter avec cette nouvelle grammaire:SELECT FROM avec clause WHERE
Select ::= 'SELECT' Column* 'FROM' LiteralString WhereClause Semicolon
Column ::= '*' | LiteralString | LiteralString Comma
WhereCaluse ::= 'WHERE' Identifier '=' LiteralValue
LiteralString ::= "'" * "'"
Identifier ::= *
LiteralInteger ::= +
LiteralValue ::= LiteralInteger | LiteralString
OpenParen ::= '('
CloseParen ::= ')'
Comma ::= ','
Semicolon ::= ';'
Elle introduit le nouveau mot-clef WHERE
qui permet dâĂ©crire une expression du type champ = value
, ce qui signifie : ârenvoie moi le champ qui possĂšde cette valeurâ.
Ajouts des tokens
Nous allons rajouter 6 tokens supplémentaires:
- PRIMARY
- KEY
- NOT
- NULL
- WHERE
- =
Jâen profite pour dĂ©finir un token technique qui permet dâutiliser lâAPI Token sans devoir dĂ©finir quelque chose de prĂ©cis Ă reconnaĂźtre.
On rajoute les matchers qui vont bien:
En nâoubliant pas de rajouter les implĂ©mentation de Size
pour ces nouveaux tokens
Forecaster UntilEnd
Son travail est simple, reconnaĂźtre la fin de la slice.
;
On consomme jusquâĂ la fin, son existence peut sembler absurde, mais il va permettre de construire des API dâalternatives de reconnaissances de pattern bien plus naturellement.
Définition des contraintes
Pour parser nos contraintes PRIMARY KEY
et NOT NUL
, il faut pouvoir les reconnaĂźtre.
Pour se faire on rajoute deux visiteurs:
PrimaryKeyConstraint
qui reconnaĂźt lâenchaĂźnement de tokens PRIMARY
suivi dâun nombre supĂ©rieur Ă 1 dâespace puis le token KEY
.
;
On fait de mĂȘme avec le NotNullConstraint
;
On fusionne les visiteurs dans une énumération
Et son pendant Constraint
associé à sa glu technique.
On peut alors en faire un Visteur.
Ajout des contraintes sur la définition de la colonne
On rajoute la possibilitĂ© dâavoir des contraintes sur une colonne.
Un groupe de contraintes est séparé par un espace blanc.
Lorsque lâon dĂ©fini des containtes, on peut se retrouver dans deux cas:
- soit câest un groupe de containtes sur un champ du schĂ©ma qui nâest pas le dernier champ du schĂ©ma : ce champ se termine par une virgule
,
- soit le groupe de contraintes est sur le dernier champ du schéma : ce champ se termine par rien du tout
Modification du visiteur Schema
Ce qui donne:
Nous sommes dĂ©sormais capables de parser les contraintes de la tables ! đ€©
Clef primaire du schéma
Une des contraintes qui le plus important pour nous Ă lâinstant prĂ©sent est PRIMARY KEY
, celle ci dispose plusieurs rÚgle de définition.
- au moins un des champs doit-ĂȘtre une clef primaire
- il ne peut pas y avoir plus dâun champ comme clef primaire dâune table
Nous allons matérialiser cela à la fois via des erreurs
et une méthode qui a pour objet de trouver la contrainte unique de clef primaire et renvoyer dans les autres cas.
Note
La primary key est un
Vec<String>
car il est possible dâassocier plus dâun champ pour construire une clef primaire.On nomme ça des clefs composites, si vous voulez prendre de lâavance sur la suite. đ
On défini un nouveau champ primary_key
sur notre Schema
.
Lors du parse on va alors appeler notre méthode parse_primary_key
depuis notre visiteur de Schéma.
Ce qui permet de parser nos schémas
Nous avons dĂ©sormais la certitude dâavoir une clef primaire unique dans la dĂ©finition du schĂ©ma de notre table.
On avance ! đ€©
Clause Where
Notre clause est extrĂȘmement simplifiĂ© par rapport Ă la rĂ©alitĂ© de SQL (chaque chose en son temps đ ).
On associe un champ et une valeur séparé par un token =
.
Notre association se matérialise par la structure
Que lâon visite
Ce qui donne
Modification de la commande SELECT
On rajoute alors notre clause where qui peut optionnellement exister
Que lâon peut alors visiter
Donnant:
Indexation des entrées
Maintenant que notre parser est amĂ©liorĂ© pour dĂ©finir la clef primaire de la table, nous allons pouvoir lâutiliser pour remplir notre âannuaireâ.
Et voici la rĂ©ponse Ă la question âQui est le nom et le numĂ©ro de notre annuaire ?â.
Notre ânomâ sera un tableau de valeur, un tuple en fait
Et notre ânumĂ©roâ sera lâID du tuple dans la table.
type PrimaryIndexes = ;
Notre âannuaireâ sera stockĂ© au niveau de la table
Lors de lâinsertion, nous allons rĂ©cupĂ©rer les valeurs des champs qui constituent la clef primaire de notre table.
Note
Si la clef primaire est impossible Ă trouver, ceci est une erreur, mĂȘme si dans les faits, cette erreur ne peut pas exister car le schĂ©ma vĂ©rifie prĂ©emptivement le tuple Ă insĂ©rer.
Il est alors possible avant insertion dans la page de venir rajouter notre clef primaire dans lâindex primaire.
Query Engine
Pour simplifier les recherches et les scans de tables et surtout pour Ă©viter que le fichier tables nâenfle dĂ©mesurĂ©ment, nous allons mettre la logique de recherche au sein dâun QueryEngine
.
Celui-ci prend une référence de Table
en paramĂštre.
On peut alors créer des comportement de recherche différent.
Dâabord le full-scan, qui est dĂ©placement du code actuelle de la table vers le QueryEngine
Puis notre recherche par index primaire.
Pour cela nous avons besoin de dâun utilitaire get_row
qui récupÚre un tuple par rapport à son ID.
Et une méthode get_by_pk
, le pk
signifie âPrimary Keyâ.
Utilisation du Query Engine
Tout étant correctement découpé, il est possible de segmenter nos recherches entre le full scan et la recherche par PK.
Testons !!
On se créé une table qui possĂšde une clef primaire âidâ.
db > (id INTEGER PRIMARY KEY, name TEXT(20), email TEXT(50));
db > INSERT INTO Users (id, name, email) VALUES (42, 'john.doe', 'john.doe@example.com');
db > INSERT INTO Users (id, name, email) VALUES (666, 'jane.doe', 'jane.doe@example.com');
db > INSERT INTO Users (id, name, email) VALUES (1, 'admin', 'admin@example.com');
On full scan la table
db > SELECT * FROM Users;
[Integer(42), Text("john.doe"), Text("john.doe@example.com")]
[Integer(666), Text("jane.doe"), Text("jane.doe@example.com")]
[Integer(1), Text("admin"), Text("admin@example.com")]
On récupÚre par PK.
db > SELECT * FROM Users WHERE id = 1;
[Integer(1), Text("admin"), Text("admin@example.com")]
db > SELECT * FROM Users WHERE id = 42;
[Integer(42), Text("john.doe"), Text("john.doe@example.com")]
db > SELECT * FROM Users WHERE id = 666;
[Integer(666), Text("jane.doe"), Text("jane.doe@example.com")]
db >
SuccĂšs total et absolu !! đ
Conclusion
Notre Query Engine a encore plein de problĂšme et notre clef primaire ne supporte pas encore les clefs composites. Mais on a un dĂ©but de quelque chose. ^^â
Dans la prochaine partie, nous verrons les expressions qui permettront de faire des recherches plus intéressantes.
Merci de votre lecture â€ïž