Skip to content
ispyisail edited this page Oct 7, 2026 · 8 revisions

The project database

QElectroTech builds an SQLite database of every project you open. It is worth being precise about what that database is, because the name invites an assumption that is not true:

The project database is a derived, in-memory cache. It is rebuilt from the .qet XML every time the project is opened, and it is never written back to the project file. The XML is the only thing that persists.

Everything else on this page follows from that one sentence.

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


1. Why it exists

Answering "list every component in this project, with manufacturer references, grouped by sheet" from a graphics scene means walking thousands of QGraphicsItems and comparing strings. Answering it from a table is a SELECT.

So QET keeps the same information in a second shape, one that is good at queries, and uses it for the things that are naturally queries:

Consumer What it reads
Nomenclature / BOM tables placed on a sheet element_nomenclature_view, via ProjectDBModel
The nomenclature query builder any of the views, assembled in ElementQueryWidget
Wiring list dialog wiring_list_view
--export-bom (CLI) element_nomenclature_view
--export-wiring (CLI) wiring_list_view
Sheet summary tables project_summary_view
Scripting's qet.query() any table or view

None of these are storage. Every one of them is a report about data that already exists in the XML.


2. Lifecycle

QETProject constructed
    └── projectDataBase constructed  β†’  createDataBase()
                                         β”œβ”€β”€ open an anonymous SQLite connection
                                         β”œβ”€β”€ CREATE TABLE Γ— 9, CREATE VIEW Γ— 4
                                         └── updateDB()
project XML read
    └── updateDB()   ← full repopulate, once, after everything is loaded
                       (read from the .qet document since PR #1142, see below)
user edits the diagram
    └── addElement / removeElement / elementInfoChanged
        addDiagram  / removeDiagram / diagramInfoChanged / diagramOrderChanged
        addConductor/ removeConductor / updateConductor
        drawingItemChanged / drawingItemDestroyed            ← incremental
project closed
    └── database discarded

QSqlDatabase::addDatabase("QSQLITE", …) is called without setDatabaseName(), so there is no file on disk for the connection to point at. During load the database's signals are blocked and a single updateDB() runs at the end, rather than one insert per object as the scene is built.

Three PRAGMAs are set immediately after opening β€” temp_store = MEMORY, journal_mode = MEMORY, synchronous = OFF. Those settings would be reckless for a database you cared about keeping. They are correct here precisely because losing the whole thing costs nothing: it is rebuilt on the next open.

QETProject::readProjectXml() logs how long each load phase took, the database rebuild among them, so the cost on a given project can be read straight from the console output rather than guessed at.

Filled from the file, not from the built sheets

Since PR #1142 (merged 2026-09-29), the full fill at load reads the .qet document instead of walking the sheets QElectroTech has built. The tables hold the same rows either way β€” a test fills them both ways on all 24 shipped examples and requires identical results β€” but the database no longer needs every sheet built before it can say what is on them. That is the first step towards opening a big project without building every sheet up front (discussion #1141).

  • Labels from formulas are computed from the document too. The file keeps the last computed value, which can be stale (K%total-%id saved as K1-1, shown as K3-1), so the formula is evaluated again rather than the saved text trusted.
  • A file that lacks something the fill needs β€” older files without saved uuids, or wires with a frozen formula text β€” falls back to the old way and says why in the log.
  • QET_DATABASE_FROM_FOLIOS=1 in the environment forces the old way.
  • Not moved yet: shapes, free texts and pictures still come from the built sheets (text sizes need fonts). Opening is not faster yet.

The incremental updates while you edit are unchanged.


3. Schema

Nine tables:

Table Key Notes
diagram uuid plus pos, the sheet order
element uuid diagram_uuid, pos, type, sub_type
diagram_info diagram_uuid one column per QETInformation::diagramInfoKeys() β€” 9 today
element_info element_uuid one column per QETInformation::elementInfoKeys() β€” 57 today
terminal (uuid, element_uuid) see Β§4
conductor uuid both endpoints as (terminal uuid, element uuid) pairs
shape uuid lines, rectangles, ellipses, polygons: type, color, fill
independent_text uuid free texts on a sheet: text, rotation
image uuid pictures: pixel_width, pixel_height of the source image

The last three share their first columns: uuid, diagram_uuid, pos (the sheet cell of the item's top-left corner, as for element), and x, y, width, height, the item's bounding box on the sheet. They follow edits as they happen, like the element and conductor tables.

Four views: element_nomenclature_view, project_summary_view, wiring_list_view, and drawing_item_view, which lists shapes, texts and pictures together with a kind column (shape, text, image) and the sheet's position in the project as folio:

SELECT kind, folio, pos, description FROM drawing_item_view ORDER BY folio, pos;

Note what the column lists mean: the schema is generated from elementInfoKeys() at runtime. Adding an element information field adds a column automatically, with no migration and no schema version β€” because there is no existing database to migrate. This is the single biggest practical consequence of the cache being derived.

The filter lives in the view, not the table

element holds every element type, slaves and sheet reports included. Restricting to "things a parts list should mention" (type IN ('simple','terminal','master','thumbnail')) happens inside element_nomenclature_view.

This was not always so, and the reason it changed is instructive: with the filter in the table, a slave element (a relay contact) was absent from the database entirely, so anything else reading the table β€” the wiring list, for instance β€” silently lost every conductor that ended on a relay contact. A nomenclature's opinion about what counts as a line item does not belong in the project's model of itself.

Queries are read-only

The SQL box of a nomenclature table, queries saved in a project and scripting's qet.query() run with SQLite's query_only setting on, so a statement that would write (INSERT, UPDATE, DROP…) is refused.

Development builds from 26–27 September 2026, between PR #1046 and its fix in PR #1066, had a side effect: a query whose rows came back unsorted β€” a UNION ALL without ORDER BY, as drawing_item_view is β€” returned only its first row, with no error. If such a build gives you one row where you expect many, add an ORDER BY.


4. Identity, and why terminals were hard

Rows need stable keys. Elements and diagrams have real UUIDs, so they are fine. So do conductors drawn or saved by a recent QElectroTech. Items saved by an older version, without a uuid, get one worked out when the project opens, the same every time, and saved from then on:

  • A symbol gets one from its type, its position on the sheet and its rotation (PR #1105).
  • A wire gets one from what it connects β€” the symbol and terminal at each end β€” never from its place in the file or its sheet's number, so inserting or moving a sheet does not change it. Once saved, reconnecting the wire keeps it (PR #1107). A worked-out uuid is never one the file already carries.
  • Shapes, free texts and pictures on a sheet, and the parts drawn inside a symbol, get one from their sheet, their kind and their order in the file. Paste and sheet duplication give the copies new ids. Older versions of QElectroTech open such files and drop the attribute when they save.

Terminals

A terminal's uuid belongs to the symbol's definition: it identifies a terminal position in a symbol β€” "the top terminal of a contactor" β€” and is therefore identical across every placed instance of that symbol. So a terminal instance is only unique as the pair (uuid, element_uuid), which is why that pair, not uuid alone, is the terminal table's primary key and what the conductor table's foreign keys reference.

Besides its name, each row has a terminal_index: the terminal's number in its symbol, counted from 0, top to bottom and then left to right β€” the number a script's addConductor() takes. It is empty when two terminals of one symbol sit at the same point, where that order is not defined. wiring_list_view carries the index and uuid of both ends (from_terminal_index, from_terminal_uuid, to_terminal_index, to_terminal_uuid), and so does --export-wiring (PR #1248): most shipped symbols leave their terminals unnamed, and these say which terminal each end of a wire is on all the same.

The collection's symbols have carried terminal uuids since 2024, but each project keeps its own copy of every symbol it uses, and older projects keep the copy they were drawn with: in the 24 example projects, 706 of the 900 stored symbols had no terminal uuid. How that is handled:

  • Terminal::stableUuid() gives a terminal without a uuid an identity derived as a UUID v5 from its position and orientation inside its symbol β€” the same basis the project format uses to match a conductor back to a terminal. Names are deliberately left out: QElectroTech rewrites a terminal named _ as unnamed, and the identity would change on the first resave.
  • Since PR #1118, opening a project gives every terminal of its stored symbols that has no uuid that same value, and saving writes it. Nothing keyed on terminals changes: the terminal table and the wire uuids above were already using it. A second terminal at the same point of a symbol gets its own value instead of sharing the first one's. The first save of an older project rewrites its wires in the form that names terminals by uuid, which QElectroTech has read since 0.8.0.
  • A wire saved against a terminal uuid is reattached on opening to a terminal with that uuid. Replacing a project's copy of a symbol ("Γ‰craser l'Γ©lΓ©ment dΓ©jΓ  intΓ©gΓ©" β€” sic β€” when placing a changed version) keeps the old terminal uuids for terminals at the same place (PR #1116), and a wire that still cannot be reattached is listed in a warning when the project opens, instead of disappearing silently.
  • Since PR #1122, the symbol editor gives the terminals of an old symbol file the same worked-out uuids when it opens it, instead of random ones, so every copy of that symbol agrees with the projects that use it. The collection's last three symbols without terminal uuids got these values too (elements #83).

projectDataBase::excludedConductorCount() reports how many conductors could not be keyed at all, counted from the live scene rather than the database β€” "precisely because the database is where these conductors are not". That is what lets a wiring list say "N wires are missing, and here is why" instead of presenting a short list as if it were complete.

This was the constraint on any future persistence work. While the database is derived, a terminal whose identity is guessed from geometry costs a cache miss; kept on disk, a guess moves whenever the terminal does. Writing the identity into the project (#1118) turns the guess into a fact the file keeps, which is what persisting anything keyed on terminals needs.


5. What follows from "derived"

Because it is derived… …this is true
Rebuilt on every open No schema version, no migrations, ever
Never written to .qet A wrong row costs nothing β€” reopen and it is gone
XML is authoritative The database cannot disagree with the drawing; if it does, the database is wrong
Discarded on close synchronous = OFF and friends are safe
Lives in one process It is not shared, not concurrent, and not a multi-user store

And the flip side, equally true:

Because it is derived… …this is also true
Nothing survives close Anything the database alone knows is lost
Rebuilt in full on open Opening cost grows with project size
Not in the file Two people cannot query the same project database

6. Seeing it for yourself

Builds compiled with QET_EXPORT_PROJECT_DB get a menu entry, Export the project's internal database, which copies the live database to a .sqlite file using SQLite's backup API. The CMake option defaults to OFF, but the official Windows, macOS, Flatpak and Snap packaging all turn it ON β€” so on a released build the entry is normally there.

The exported file is a snapshot for inspection. Editing it changes nothing: nothing ever reads it back.

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

7. What it is not

It is not the project file, not a shared database a team connects to, and not the component-database-plus-drawing-view architecture of tools like EPLAN. A project is still one XML file; the database is a query index over it that lives as long as the window is open.

Whether it should stay that way is a live question β€” see the Vision page and the Development Roadmap. Any move toward persisting it is a change to the file format, with the terminal-identity problem in Β§4 as its first real obstacle.

Until such a decision is taken, there is a rule of thumb worth following when adding features: do not create state that only the XML knows about, and do not create state that only the database knows about. The first makes the cache incomplete; the second cannot survive a close.

Getting Started

Home

🌐 Languages β€” English Β· FranΓ§ais Β· Deutsch

Downloads

Windows without admin rights β€” the portable archive, no installer

Quick Start Guide

User Manual

FAQ

Tips & Tricks

Guides

Conductors β€” wire properties, what feeds which export, cables, and hops where wires cross

Wires per terminal β€” limit the wires on a terminal, chain wiring instead of stars

Printing and exporting β€” paper, PDF, images, and what each path does differently

Linking elements β€” master, slave, terminal

PLC modules β€” I/O tables and linking a wire to a specific point

Using the element editor β€” drawing tools, saving, checks

Grid size and element size β€” why symbols aren't all the same scale, and scaling one without leaving the grid

Preferences reference β€” what each settings page does

Saving and loading settings β€” your whole setup in one file, to copy or keep

Keyboard-only control β€” mouseless QET, and what still needs a mouse

Mouse modifiers β€” what Shift, Ctrl and Alt change while you drag

3D mouse β€” SpaceMouse pan, zoom and buttons

Aligning items β€” snap symbols back to the grid, or line them up

Pictures on a sheet β€” labels, crop, transparency, what they cost in the file

Arcs and curved wires β€” the Arc tool, pulling an arc in or out, rounding a corner with a fillet, dashed arcs for lighting layouts

Grouping items β€” select, move and copy several items as one

Finding your place on a sheet β€” go to a cell like B13 or 4-B7, keep the headers in sight, show the cell limits, zoom and pan

Showing and hiding kinds of items β€” hide texts, wire numbers, shapes, pictures, tables or cross-references on every sheet

Drawing faster β€” place without dragging, the S shortcut bar, command search, gestures

Customising QElectroTech β€” keys, toolbar size and contents, the gesture ring (partly pending)

Managing collections β€” folders, writability, building your own shortlist

Templates β€” reusable multi-element blocks, placed by double-click or drag

Search & Replace β€” bulk property changes

Building a nomenclature query β€” the BOM/summary table builder

Linking wires across pages β€” sheet reports

Variables & formulas β€” %f, %{label}, sequences

Auto-numbering β€” schemes, sequences, freezing

Terminal strips β€” strips, levels, bridges

Title block templates β€” the .titleblock format

Importing EPLAN parts (.edz) β€” EPLAN Data Portal

DXF import & export β€” two unrelated features, one format; command-line export and layers

The project database β€” the in-memory SQLite cache

File formats
Elements XML
Project XML
Development

Building from Source

Contributing Code

Automating QET β€” CLI, XML formats, external tools

CLI Reference β€” command line usage

JavaScript Scripting β€” --run, geometry editing, undo

MCP server β€” let an AI assistant read, verify and edit projects

Connecting an AI assistant β€” setup for Claude, Copilot, Gemini, Codex, Cursor, LM Studio

Script buttons β€” stored scripts with an icon, by hand or by an assistant

Live mode β€” an assistant working in the open project while you watch

Macro recorder β€” record a task by hand, for an assistant to script

Development Roadmap

Vision β€” proposal, under discussion

Developer Tools

About

Features

History

Community

License

Contributing to this Wiki

Clone this wiki locally