Clone
1
project_database DE
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.

Die Projektdatenbank

QElectroTech baut für jedes geöffnete Projekt eine SQLite-Datenbank auf. Es lohnt sich, genau zu sagen, was diese Datenbank ist, denn ihr Name legt eine Annahme nahe, die nicht zutrifft:

Die Projektdatenbank ist ein abgeleiteter Cache im Arbeitsspeicher. Sie wird bei jedem Öffnen des Projekts aus dem .qet-XML neu aufgebaut und nie in die Projektdatei zurückgeschrieben. Allein das XML ist dauerhaft.

Alles Weitere auf dieser Seite folgt aus diesem einen Satz.

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


1. Wozu sie da ist

„Liste alle Bauteile dieses Projekts mit Herstellerreferenzen, nach Folio gruppiert“ aus einer Grafikszene zu beantworten, heißt Tausende von QGraphicsItem zu durchlaufen und Zeichenketten zu vergleichen. Aus einer Tabelle beantwortet, ist es ein SELECT.

QET hält dieselbe Information deshalb in einer zweiten Form vor, die für Abfragen taugt, und nutzt sie für das, was von Natur aus eine Abfrage ist:

Nutzer Was gelesen wird
Stücklistentabellen auf einem Folio element_nomenclature_view, über ProjectDBModel
Der Abfrage-Assistent der Stückliste jede der Sichten, zusammengesetzt in ElementQueryWidget
Dialog der Verdrahtungsliste wiring_list_view
--export-bom (Kommandozeile) element_nomenclature_view
--export-wires, --export-cables wiring_list_view
Folio-Übersichtstabellen project_summary_view

Nichts davon ist Speicherung. Jedes davon ist ein Bericht über Daten, die im XML bereits vorliegen.


2. Lebenszyklus

QETProject erzeugt
    └── projectDataBase erzeugt  →  createDataBase()
                                     ├── öffnet eine namenlose SQLite-Verbindung
                                     ├── CREATE TABLE × 6, CREATE VIEW × 3
                                     └── updateDB()
Projekt-XML gelesen
    └── updateDB()   ← vollständiger Neuaufbau, einmal, nach allem Laden
Benutzer bearbeitet das Folio
    └── addElement / removeElement / elementInfoChanged
        addDiagram  / removeDiagram / diagramInfoChanged / diagramOrderChanged
        addConductor/ removeConductor / updateConductor      ← inkrementell
Projekt geschlossen
    └── Datenbank verworfen

QSqlDatabase::addDatabase("QSQLITE", …) wird ohne setDatabaseName() aufgerufen; es gibt also keine Datei auf der Platte, auf die die Verbindung zeigen könnte. Während des Ladens sind die Signale der Datenbank blockiert, und am Ende läuft ein einziges updateDB() statt eines Einfügevorgangs je Objekt während des Szenenaufbaus.

Direkt nach dem Öffnen werden drei PRAGMAs gesetzt — temp_store = MEMORY, journal_mode = MEMORY, synchronous = OFF. Für eine Datenbank, an der einem liegt, wären diese Einstellungen leichtsinnig. Hier sind sie genau deshalb richtig, weil ein Totalverlust nichts kostet: Beim nächsten Öffnen wird ohnehin neu aufgebaut.

QETProject::readProjectXml() protokolliert die Dauer jeder Ladephase, den Datenbankaufbau eingeschlossen. Die Kosten für ein bestimmtes Projekt lassen sich also direkt der Konsolenausgabe entnehmen, statt geschätzt zu werden.


3. Schema

Sechs Tabellen:

Tabelle Schlüssel Anmerkungen
diagram uuid dazu pos, die Folio-Reihenfolge
element uuid diagram_uuid, pos, type, sub_type
diagram_info diagram_uuid je eine Spalte pro QETInformation::diagramInfoKeys() — heute 9
element_info element_uuid je eine Spalte pro QETInformation::elementInfoKeys() — heute 57
terminal (uuid, element_uuid) siehe §4
conductor uuid beide Enden als Paare (Klemmen-uuid, Element-uuid)

Drei Sichten: element_nomenclature_view, project_summary_view, wiring_list_view.

Man beachte, was diese Spaltenlisten bedeuten: das Schema wird zur Laufzeit aus elementInfoKeys() erzeugt. Ein neues Elementinformationsfeld fügt automatisch eine Spalte hinzu, ohne Migration und ohne Schemaversion — denn es gibt keine bestehende Datenbank zu migrieren. Das ist die praktisch wichtigste Folge daraus, dass der Cache abgeleitet ist.

Der Filter sitzt in der Sicht, nicht in der Tabelle

element enthält alle Elementtypen, Slaves und Folioverweise eingeschlossen. Die Einschränkung auf „was eine Stückliste nennen sollte“ (type IN ('simple','terminal','master','thumbnail')) geschieht innerhalb von element_nomenclature_view.

Das war nicht immer so, und der Grund für die Änderung ist lehrreich: Lag der Filter in der Tabelle, fehlte ein Slave-Element (ein Relaiskontakt) in der Datenbank vollständig — jeder andere Leser der Tabelle, etwa die Verdrahtungsliste, verlor damit stillschweigend jeden Leiter, der an einem Relaiskontakt endete. Die Ansicht einer Stückliste darüber, was eine Position ist, gehört nicht in das Modell, das ein Projekt von sich selbst hat.


4. Identität, und warum Klemmen schwierig sind

Zeilen brauchen stabile Schlüssel. Elemente und Folios haben echte UUIDs, also kein Problem. Klemmen nicht.

Terminal::uuid() stammt aus der Katalogdefinition .elmt. Es bezeichnet eine Klemmenposition in einem Symbol — „die obere Klemme eines Schützes“ — und ist daher für jede platzierte Instanz dieses Symbols identisch. Zudem ist es bei jedem Element leer, das vor Einführung dieses Feldes erstellt wurde, also beim größten Teil der installierten Sammlung.

Zwei Konsequenzen, beide behandelt:

  • Eine Klemmeninstanz ist nur als Paar (uuid, element_uuid) eindeutig. Deshalb ist dieses Paar — und nicht uuid allein — der Primärschlüssel der Klemmentabelle und das Ziel der Fremdschlüssel der Leitertabelle.
  • Terminal::stableUuid() liefert eine Identität, wenn die Definition keine vorgibt: als UUID v5, abgeleitet aus lokaler Position und Ausrichtung der Klemme innerhalb ihres Elements — derselben Grundlage, die das Projektformat ohnehin schon nutzt, um einen Leiter einer Klemme zuzuordnen. Namen bleiben bewusst außen vor, denn QET schreibt eine Klemme namens _ als unbenannt zurück, wodurch sich die Identität beim ersten erneuten Speichern ändern würde.

projectDataBase::excludedConductorCount() meldet, wie viele Leiter überhaupt nicht indizierbar waren — gezählt aus der lebenden Szene, nicht aus der Datenbank, „genau deshalb, weil die Datenbank der Ort ist, an dem diese Leiter nicht sind“. Das erlaubt einer Verdrahtungsliste zu sagen „N Drähte fehlen, und zwar aus diesem Grund“, statt eine unvollständige Liste als vollständig auszugeben.

Das ist die Randbedingung, die man bei jeder künftigen Persistenzarbeit im Blick behalten muss. Solange die Datenbank abgeleitet ist, kostet eine aus der Geometrie erratene Klemmenidentität einen Cache-Fehlschlag. Als Dateiformat wird aus derselben Schätzung eine endgültige, einmalige Migration der Projekte aller Anwender.


5. Was aus „abgeleitet“ folgt

Weil sie abgeleitet ist… …gilt dies
Bei jedem Öffnen neu aufgebaut Keine Schemaversion, keine Migrationen, nie
Nie ins .qet geschrieben Eine falsche Zeile kostet nichts — neu öffnen, und sie ist weg
Das XML ist maßgeblich Die Datenbank kann der Zeichnung nicht widersprechen; tut sie es doch, irrt die Datenbank
Beim Schließen verworfen synchronous = OFF und Verwandtes sind unbedenklich
Lebt in einem Prozess Sie ist weder geteilt noch nebenläufig noch mehrbenutzerfähig

Und die Kehrseite, ebenso wahr:

Weil sie abgeleitet ist… …gilt auch dies
Nichts überlebt das Schließen Alles, was nur die Datenbank weiß, geht verloren
Beim Öffnen vollständig neu aufgebaut Die Öffnungskosten wachsen mit der Projektgröße
Nicht in der Datei enthalten Zwei Personen können nicht dieselbe Projektdatenbank abfragen

6. Selbst nachsehen

Mit QET_EXPORT_PROJECT_DB übersetzte Programme haben einen Menüeintrag, Interne Projektdatenbank exportieren, der die lebende Datenbank über die Backup-API von SQLite in eine .sqlite-Datei kopiert. Die CMake-Option steht standardmäßig auf OFF, doch die offiziellen Pakete für Windows, macOS, Flatpak und Snap schalten sie alle ein — in einer veröffentlichten Version ist der Eintrag also normalerweise vorhanden.

Die exportierte Datei ist eine Momentaufnahme zur Einsicht. Sie zu bearbeiten ändert nichts: Niemand liest sie je zurück.

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

7. Was sie nicht ist

Sie ist nicht die Projektdatei, keine gemeinsame Datenbank, mit der sich ein Team verbindet, und nicht die Architektur aus Bauteildatenbank plus Zeichnungsansicht, wie sie Werkzeuge wie EPLAN haben. Ein Projekt bleibt eine einzige XML-Datei; die Datenbank ist ein Abfrageindex darüber, der lebt, solange das Fenster offen ist.

Ob das so bleiben soll, ist eine offene Frage — siehe die Seiten Vision und Entwicklungs-Roadmap. Jeder Schritt hin zu Persistenz ist eine Änderung des Dateiformats, und das Problem der Klemmenidentität aus §4 ist deren erste echte Hürde.

Bis eine solche Entscheidung fällt, lohnt beim Hinzufügen von Funktionen eine Faustregel: Erzeugen Sie keinen Zustand, den nur das XML kennt, und keinen, den nur die Datenbank kennt. Ersteres macht den Cache unvollständig; Letzteres überlebt kein Schließen.