Clone
1
project_database FR
ispyisail edited this page 2026-09-11 21:24:08 +12:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

La base de données du projet

QElectroTech construit une base de données SQLite pour chaque projet ouvert. Il vaut la peine d'être précis sur ce qu'est cette base, car son nom invite à une supposition qui est fausse :

La base de données du projet est un cache dérivé, en mémoire. Elle est reconstruite à partir du XML .qet à chaque ouverture du projet, et elle n'est jamais réécrite dans le fichier projet. Le XML est la seule chose qui persiste.

Tout le reste de cette page découle de cette seule phrase.

Source : sources/dataBase/projectdatabase.{h,cpp}.


1. Pourquoi elle existe

Répondre à « liste tous les composants de ce projet, avec leurs références fabricant, groupés par folio » depuis une scène graphique suppose de parcourir des milliers de QGraphicsItem et de comparer des chaînes. Y répondre depuis une table, c'est un SELECT.

QET conserve donc la même information sous une seconde forme, adaptée aux requêtes, et s'en sert pour ce qui est naturellement une requête :

Consommateur Ce qu'il lit
Tableaux de nomenclature placés sur un folio element_nomenclature_view, via ProjectDBModel
Le constructeur de requêtes de nomenclature n'importe laquelle des vues, assemblée dans ElementQueryWidget
Dialogue de liste de câblage wiring_list_view
--export-bom (ligne de commande) element_nomenclature_view
--export-wires, --export-cables wiring_list_view
Tableaux récapitulatifs de folios project_summary_view

Aucun de ces usages n'est du stockage. Chacun est un rapport sur des données qui existent déjà dans le XML.


2. Cycle de vie

QETProject construit
    └── projectDataBase construit  →  createDataBase()
                                       ├── ouvre une connexion SQLite anonyme
                                       ├── CREATE TABLE × 6, CREATE VIEW × 3
                                       └── updateDB()
XML du projet lu
    └── updateDB()   ← repeuplement complet, une fois, tout étant chargé
l'utilisateur édite le folio
    └── addElement / removeElement / elementInfoChanged
        addDiagram  / removeDiagram / diagramInfoChanged / diagramOrderChanged
        addConductor/ removeConductor / updateConductor      ← incrémental
projet fermé
    └── base abandonnée

QSqlDatabase::addDatabase("QSQLITE", …) est appelé sans setDatabaseName() : il n'y a donc aucun fichier sur disque vers lequel la connexion pointerait. Pendant le chargement, les signaux de la base sont bloqués et un unique updateDB() s'exécute à la fin, plutôt qu'une insertion par objet au fur et à mesure de la construction de la scène.

Trois PRAGMA sont posés juste après l'ouverture — temp_store = MEMORY, journal_mode = MEMORY, synchronous = OFF. Ces réglages seraient inconsidérés pour une base à laquelle on tient. Ils sont corrects ici précisément parce que tout perdre ne coûte rien : elle est reconstruite à l'ouverture suivante.

QETProject::readProjectXml() journalise la durée de chaque phase du chargement, dont la reconstruction de la base : le coût sur un projet donné se lit donc directement dans la sortie console au lieu d'être supposé.


3. Schéma

Six tables :

Table Clé Remarques
diagram uuid plus pos, l'ordre des folios
element uuid diagram_uuid, pos, type, sub_type
diagram_info diagram_uuid une colonne par QETInformation::diagramInfoKeys() — 9 aujourd'hui
element_info element_uuid une colonne par QETInformation::elementInfoKeys() — 57 aujourd'hui
terminal (uuid, element_uuid) voir §4
conductor uuid les deux extrémités en paires (uuid de borne, uuid d'élément)

Trois vues : element_nomenclature_view, project_summary_view, wiring_list_view.

Remarquez ce que signifient ces listes de colonnes : le schéma est engendré à l'exécution à partir de elementInfoKeys(). Ajouter un champ d'information d'élément ajoute une colonne automatiquement, sans migration ni version de schéma — parce qu'il n'existe aucune base à migrer. C'est la conséquence pratique la plus importante du fait que le cache soit dérivé.

Le filtre est dans la vue, pas dans la table

element contient tous les types d'éléments, esclaves et renvois de folio compris. La restriction aux « choses qu'une nomenclature doit mentionner » (type IN ('simple','terminal','master','thumbnail')) se fait à l'intérieur de element_nomenclature_view.

Il n'en a pas toujours été ainsi, et la raison du changement est instructive : avec le filtre dans la table, un élément esclave (un contact de relais) était totalement absent de la base, si bien que tout autre lecteur de la table — la liste de câblage, par exemple — perdait silencieusement chaque conducteur aboutissant à un contact de relais. L'opinion d'une nomenclature sur ce qui constitue une ligne n'a pas sa place dans le modèle que le projet a de lui-même.


4. L'identité, et pourquoi les bornes sont difficiles

Les lignes ont besoin de clés stables. Les éléments et les folios ont de vrais UUID : pas de problème. Les bornes, non.

Terminal::uuid() provient de la définition .elmt du catalogue. Il identifie une position de borne dans un symbole — « la borne du haut d'un contacteur » — et il est donc identique pour chaque instance placée de ce symbole. Il est en outre vide pour tout élément créé avant l'existence de ce champ, c'est-à-dire pour l'essentiel de la collection installée.

Deux conséquences, toutes deux traitées :

  • Une instance de borne n'est unique que par la paire (uuid, element_uuid) : c'est pourquoi cette paire, et non uuid seul, est la clé primaire de la table des bornes et la cible des clés étrangères de la table des conducteurs.
  • Terminal::stableUuid() fournit une identité lorsque la définition n'en donne aucune, dérivée en UUID v5 à partir de la position locale et de l'orientation de la borne dans son élément — la même base que celle que le format de projet utilise déjà pour rattacher un conducteur à une borne. Les noms sont délibérément exclus de la dérivation, car QET réécrit une borne nommée _ comme non nommée, ce qui changerait son identité dès la première réécriture.

projectDataBase::excludedConductorCount() indique combien de conducteurs n'ont pu être indexés du tout, en comptant depuis la scène vivante et non depuis la base — « précisément parce que la base est là où ces conducteurs ne sont pas ». C'est ce qui permet à une liste de câblage d'annoncer « N fils manquent, et voici pourquoi » au lieu de présenter une liste incomplète comme si elle était complète.

C'est la contrainte à garder en tête pour tout travail futur sur la persistance. Tant que la base est dérivée, une borne dont l'identité est devinée par la géométrie coûte un défaut de cache. Dans un format de fichier, la même supposition devient une migration définitive, en une seule fois, des projets de tout le monde.


5. Ce qui découle de « dérivée »

Parce qu'elle est dérivée… …ceci est vrai
Reconstruite à chaque ouverture Aucune version de schéma, aucune migration, jamais
Jamais écrite dans le .qet Une ligne erronée ne coûte rien — rouvrez, elle a disparu
Le XML fait autorité La base ne peut pas contredire le dessin ; si c'est le cas, c'est la base qui a tort
Abandonnée à la fermeture synchronous = OFF et consorts sont sans danger
Vit dans un seul processus Elle n'est ni partagée, ni concurrente, ni multi-utilisateur

Et le revers, tout aussi vrai :

Parce qu'elle est dérivée… …ceci est vrai aussi
Rien ne survit à la fermeture Tout ce que la base seule sait est perdu
Reconstruite intégralement à l'ouverture Le coût d'ouverture croît avec la taille du projet
Absente du fichier Deux personnes ne peuvent pas interroger la même base de projet

6. La voir par soi-même

Les compilations avec QET_EXPORT_PROJECT_DB disposent d'une entrée de menu, Exporter la base de donnée interne du projet, qui copie la base vivante dans un fichier .sqlite via l'API de sauvegarde de SQLite. L'option CMake vaut OFF par défaut, mais les empaquetages officiels Windows, macOS, Flatpak et Snap l'activent tous — sur une version publiée, l'entrée est donc normalement présente.

Le fichier exporté est un instantané, pour inspection. Le modifier ne change rien : rien ne le relit jamais.

sqlite3 monprojet.sqlite ".schema"
sqlite3 monprojet.sqlite "SELECT label, designation FROM element_nomenclature_view LIMIT 20;"

7. Ce qu'elle n'est pas

Ce n'est pas le fichier projet, ni une base partagée à laquelle une équipe se connecte, ni l'architecture « base de composants plus vue de dessin » d'outils comme EPLAN. Un projet reste un unique fichier XML ; la base est un index de requêtes au-dessus de lui, qui vit tant que la fenêtre est ouverte.

Savoir si cela doit rester ainsi est une question ouverte — voir les pages Vision et Feuille de route. Tout mouvement vers une persistance est une modification du format de fichier, dont le problème d'identité des bornes du §4 est le premier véritable obstacle.

Tant qu'une telle décision n'est pas prise, une règle empirique mérite d'être suivie lorsqu'on ajoute une fonctionnalité : ne créez pas d'état que seul le XML connaît, et ne créez pas d'état que seule la base connaît. Le premier rend le cache incomplet ; le second ne survit pas à une fermeture.