Il est amusant de constater à quel point la technologie informatique exotique finit par échouer ou devient courante. À une époque, avoir plus d’un utilisateur sur un ordinateur à la fois était de la haute technologie, par exemple. Ensuite, il y a des choses qui n’ont pas fait grand bruit, comme l’affichage vectoriel ou la mémoire adressable par le contenu. L’utilisation du stockage de masse – en particulier des lecteurs de disque – dans les ordinateurs, est cependant devenue très répandue. Mais à une époque, c’était une technique exotique et elle n’était pas aussi simple qu’aujourd’hui.

Cependant, je suis surpris que le système de fichiers tel que nous le connaissons n’ait pas beaucoup changé au fil des ans. Bien sûr, par rapport aux années 1960, par exemple, nous avons de bien meilleures fonctionnalités. Et nous avons beaucoup d’améliorations concernant la vitesse, l’encodage, le cryptage, la compression, etc. Mais la nature fondamentale de la façon dont nous stockons et accédons aux fichiers dans les programmes informatiques est stagnante. Mais cela n’a pas à être. Nous connaissons de meilleures façons d’organiser les données, mais pour une raison quelconque, la plupart d’entre nous ne les utilisons pas dans nos programmes. Il s’avère, cependant, que c’est assez simple et je vais vous montrer comment, avec une application jouet, cela pourrait être le début d’une base de données pour les composants électroniques de mon laboratoire.

Vous pouvez stocker une base de données comme celle-ci dans un fichier délimité par des virgules ou en utilisant quelque chose comme JSON. Mais je vais utiliser une base de données SQLite complète pour éviter d’avoir un serveur de base de données lourd et toute la douleur que cela implique. Va-t-il remplacer la base de données derrière le système de réservation des compagnies aériennes ? Non. Mais cela fonctionnera-t-il pour la plupart de ce que vous êtes susceptible de faire ? Tu paries.

Abstraction

Si vous y réfléchissez, le système de fichiers n’est rien de plus qu’une abstraction sur le lecteur de disque. Normalement, nous ne savons pas ou ne nous soucions pas où exactement hello.c est stocké. Nous ne nous soucions même pas s’il est crypté ou compressé. Il peut être récupéré sur un réseau ou tous ses éléments peuvent être dispersés de manière aléatoire sur le disque. Nous ne nous en soucions généralement pas. Et si vous faisiez abstraction du système de fichiers lui-même ?

C’est à peu près l’idée d’une base de données. Si j’ai une liste de, disons, composants électroniques, je pourrais les stocker dans un fichier délimité par des virgules et le lire avec une feuille de calcul. Ou je pourrais utiliser une base de données complète. Le problème avec les bases de données est qu’elles nécessitent traditionnellement un logiciel serveur comme MySQL, SQLServer ou Oracle, par exemple. Vous pouvez faire abstraction de l’interface de la base de données, mais c’est une solution assez lourde par rapport à la simple ouverture d’un fichier et à son utilisation normale.

Cependant, il existe une bibliothèque fréquemment utilisée appelée SQLite qui fournit une base de données assez robuste qui peut vivre dans un seul fichier sans serveur externe ni maintenance. Il y a bien sûr des limitations, mais pour de nombreux programmes simples, cela peut apporter les avantages d’une base de données sans les frais généraux et les dépenses.

Le bon outil pour le bon travail

Bien sûr, il y a des limites. Cependant, si vous utilisez votre propre format de fichier pour quelque chose, vous pouvez envisager de passer à SQLite et de le gérer comme une base de données. Selon le site Web du projet, cela pourrait en fait économiser de l’espace et augmenter la vitesse d’accès. De plus, une fois que vous avez pris le coup, c’est plus simple. Il est également plus facile d’évoluer ultérieurement si vous décidez de passer à une vraie base de données.

Si vous stockez d’énormes bases de données (comme à l’échelle du téraoctet) ou si vous avez besoin de nombreux utilisateurs simultanés, en particulier pour écrire dans la base de données, ce n’est peut-être pas pour vous. Le site Web de SQLite a une bonne page sur les utilisations qui sont bonnes et celles qui ne sont pas optimales pour la bibliothèque.

Un autre avantage : il existe un programme en ligne de commande (et quelques variantes d’interface graphique comme le navigateur dans l’image d’accompagnement) qui vous permet de travailler avec des bases de données SQLite sans écrire de code. Ainsi, vous pouvez faire des choses comme remplir vos données ou examiner votre base de données sans avoir à écrire du tout SQL. Pour un format de fichier personnalisé, vous devrez probablement tout faire vous-même ou remplir et déboguer les données avec un outil générique qui ne connaît pas vos données spécifiques.

Ma tâche

Je ne veux pas développer une application entière dans un article, ni enseigner SQL – le langage de requête structuré que la plupart des bases de données incluent l’utilisation de SQLite. Mais je veux vous montrer à quel point il est facile de démarrer une base de données électronique simple en utilisant C. Le code C s’avérera être le moindre de nos problèmes. Les deux choses que vous voudrez le plus comprendre sont comment structurer les données – le schéma de la base de données – et comment remplir les données initiales. Même si vous souhaitez que votre programme ajoute éventuellement des données, il est préférable de commencer avec un peu de données au départ pour que votre programme fonctionne.

Principes de base de la base de données

Une base de données relationnelle moderne possède une ou plusieurs tables. Chaque table contient des lignes de données. Une ligne a une ou plusieurs colonnes et chaque colonne a un type de données. Par exemple, vous pouvez avoir une colonne de texte pour le numéro de série, une valeur numérique réelle pour la tension du point de test et un booléen pour la réussite/l’échec.

Chaque table a un identifiant unique par ligne. La base de données vous en fournira un si vous ne le faites pas, mais généralement, vous voudrez fournir vous-même cet identifiant unique. La base de données vous aidera en incrémentant automatiquement le numéro et en s’assurant qu’il est unique pour chaque ligne.

Si c’était tout ce qu’il y avait à faire, il n’y aurait pas beaucoup d’avantages par rapport à un fichier délimité par des virgules. Mais nous pouvons faire beaucoup de choses mieux une fois que nous avons cette structure organisationnelle. Par exemple, il est facile de demander à la base de données de trier les éléments ou de sélectionner les trois tensions les plus élevées du tableau.

Cependant, l’un des principaux avantages d’une base de données est de pouvoir effectuer des jointures. Supposons que j’aie une liste de composants : une carte PC, une résistance, un support de batterie et une LED. J’ai un tableau qui a une ligne correspondant à chacun d’eux. Supposons maintenant que je veuille avoir une table d’assemblages constitués de composants.

Je pourrais adopter une approche simple :

Table Component
ID    Name
===========
1     PCB
2     Resistor
3     LED
4     Battery Holder

Table Assembly
ID    Name       Components
============================
1     Blink1     PCB, Resistor, LED, Battery Holder
2     Blink2     PCB, Resistor, LED, Resistor, LED, Battery Holder



That's ugly and wasteful. A better approach would be to use three tables:

Table Component
ID Name
===========
1 PCB
2 Resistor
3 LED
4 Battery Holder

Table Assembly
ID Name 
=========
1 Blink1 
2 Blink2 

Table Assembly_Parts
ID    Component    Quan
=======================
1     1            1
1     2            1
1     3            1
1     4            1
2     1            1
2     2            2
2     3            2
2     4            1

À l’aide d’une opération de jointure, vous pouvez lier ces tables ensemble pour générer ce qui équivaut à la première table sans dupliquer beaucoup de données.

Pour ma base de données de jouets, je vais donc créer trois tables : part contiendra les pièces que j’ai. le partnums table contiendra des types de pièces (par exemple un 7805 vs un 2N2222 ou un CDP1802. Enfin, un locations table me dira où je range les choses. Il y a d’autres façons de structurer cela. Par exemple, il pourrait y avoir une table pour stocker les types d’empreintes : un 2N2222 peut être dans un TO92 ou un montage en surface. De plus, je vais créer une vue qui montre tout déroulé comme dans le premier exemple. Une vue est quelque chose qui n’est pas stocké mais agit comme une table pour plus de commodité. En réalité, il s’agit simplement d’une requête sur la base de données avec laquelle vous pouvez travailler.

Il y a bien plus, bien sûr. Il y a des jointures internes et externes et beaucoup d’autres détails et nuances. Heureusement, il y a beaucoup de matériel à lire sur les bases de données sur le Web, y compris la documentation SQLite.

Juste assez de SQL

Pour nos besoins, nous n’allons utiliser qu’une poignée d’instructions SQL : create, insert, et select. Il y a un exécutable, sqlite3, où vous pouvez saisir des commandes de base de données. Vous pouvez fournir le nom de la base de données sur la ligne de commande et c’est le moyen le plus simple de procéder. Utiliser .exit quand vous voulez sortir.

Vous pouvez probablement comprendre la syntaxe SQL car elle est assez détaillée :

create table part ( id integer not null primary key, name text, partnum integer, value text, 
   units text, quantity integer, photo blob, data text, location integer, footprint text);
create table partnums (id integer not null primary key, partnum text, desc text);

create table locations (id integer not null primary key, location text, desc text);

create view full as select part.id, name, partnums.partnum as part_number, value, units, 
   quantity, data, locations.location as location, footprint from part 
   inner join partnums on part.partnum = partnums.id inner join locations on locations.id=part.location

Je viens de faire ces appels dans le programme de ligne de commande sqlite3 bien que j’aurais pu utiliser l’interface graphique ou – si je le voulais – je pouvais faire en sorte que mon programme C exécute ces commandes. J’ai également utilisé la ligne de commande pour insérer quelques enregistrements de test. Par example:

insert into locations (location,desc) values ("Shop - storage II","Storage over computer desk in shop");
insert into partnums(partnum,desc) values("R.25W","Quarter Watt Resistor");
insert into part(partnum,quantity,location,value,units) values (2,111,1,"10K","ohms");

Pour récupérer les données, vous utiliserez la commande select :

select * from part;

select partnum, quantity from part where quantity<5;

Si vous voulez en savoir plus, il existe de nombreux tutoriels SQL sur le Web.

Programmation!

Jusqu’à présent, rien de tout cela n’a nécessité de programmation. En supposant que vous ayez le libsqlite3-dev package ou son équivalent, vous n’avez pas besoin de grand-chose pour ajouter des fonctions de base de données à votre programme C. Vous devrez inclure sqlite3.h. Si vous ne le trouvez pas, vous n’avez probablement pas installé les fichiers de développement. Vous devrez également établir un lien avec libsqlite3. Pour un simple projet à fichier unique, ce makefile vous aidera probablement à démarrer :

CC=gcc
CFLAGS+=-std=c99 -g
LDFLAGS=-g
LDLIBS+=-lsqlite3

edatabase : main

main : main.c

Le code lui-même est simple. Vous devez ouvrir le fichier de base de données (sqllite3_open). Au lieu d’un fichier, vous pouvez passer « :memory » pour obtenir une base de données en mémoire qui ne durera pas au-delà de la durée de vie de votre programme. L’appel vous donnera une poignée de retour à votre base de données. Ensuite, vous devez analyser ou préparer l’instruction SQL que vous souhaitez exécuter. Il peut s’agir de n’importe quel SQL que nous avons exécuté via l’interface ou de nombreuses autres instructions SQL. Dans mon cas, je veux extraire les données de la vue complète et les afficher, je vais donc analyser :

select * from full;

Enfin, tu appelleras sqlite3_step et pendant qu’il revient SQLITE_ROW, vous pouvez traiter la ligne à l’aide d’appels tels que sqlite3_column_text. A la fin, vous finalisez la base de données et la fermez. Voici le code avec la gestion des erreurs supprimée :

#include <sqlite3.h>
#include <stdio.h>

int main(int argc, char *argv[])
   {
   sqlite3 *db;
   sqlite3_stmt *sql;
   int rv;

   rv=sqlite3_open("parts.db",&db);
   rv=sqlite3_prepare_v2(db, "SELECT * from full", -1, &sql, NULL);
   do
     {
     rv=sqlite3_step(sql);
     if (rv==SQLITE_ROW)
        {
        printf("%s,",sqlite3_column_text(sql,0));
        printf("%sn",sqlite3_column_text(sql,2));
        }
     } while (rv==SQLITE_ROW); 
   sqlite3_finalize(sql);
   sqlite3_close(db);
   return 0;
}

Ou, jetez un œil au code complet. Dans un cas où vous ne vous souciiez pas de parcourir les rangées, vous auriez pu appeler sqlite3_exec. Même la documentation admet qu’il ne s’agit que d’une enveloppe autour d’une préparation, d’une étape et d’une finalisation afin que vous puissiez simplement passer une chaîne et vous attendre à ce que cela fonctionne.

Bien sûr, il y a beaucoup plus d’appels. Par exemple, vous pouvez appeler sqlite_column_int ou d’autres appels pour obtenir des types particuliers. Vous pouvez lier des paramètres aux appels SQL pour définir des valeurs au lieu de créer une chaîne. Mais cela vous montre à quel point il peut être facile de faire un simple programme SQLite.

Alors la prochaine fois que vous vous retrouverez à inventer un nouveau format de fichier, pensez plutôt à utiliser SQLite. Vous obtiendrez des outils gratuits et une fois que vous aurez appris SQL, vous découvrirez qu’il y a beaucoup de choses que vous pouvez faire sans écrire de code réel autre que différentes commandes SQL. Vous pouvez même utiliser des branches de type Git pour conserver les versions de votre base de données. Là encore, certaines personnes utilisent git comme base de données, mais nous ne le suggérons pas.