From 956836458a77b257e5db070c1c6f56b4a66f0bae Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 12 Sep 2026 13:50:24 +1200 Subject: [PATCH] Rebuild the project database once per load instead of once per table model ProjectDBModel::setQuery() calls projectDataBase::updateDB(), which drops and repopulates every table in the database. The rebuild does not depend on the query, so each table model that queries the database while a project is being read triggers another complete repopulate of the same content. Opening a 100 folio project ran updateDB() 26 times, 9.9 s of a 15.7 s load. Two changes, because the first alone is not enough: setUpdateBlocked() lets a bulk operation suppress the rebuild and do it once when it is done. readProjectXml() already wrapped the load in blockSignals(true) "to avoid hundreds of unnecessary emitted signal", but that suppresses only the signal, not the work it announces; this extends the same intent to the work. Both early returns in readProjectXml() sit before the block, so no path leaves the database permanently blocked. Further rebuilds are triggered after readProjectXml() returns, where the load phase timers cannot see them -- with only the block in place updateDB() still ran 5 times on examples/industrial.qet. So the database now also tracks whether anything has changed since the last rebuild, and skips repopulating when nothing has. dataBaseUpdated() is still emitted in that case: callers and models rely on it to refresh, and what they read back is the same either way. Every method of the class that writes rows marks the flag; from outside, the database is reachable only through newQuery(), and all five call sites read. Repeating the rebuild was wasteful rather than wrong -- each populate*Table() begins with a DELETE -- so this changes no output. Verified byte identical --info on all 23 example projects, and identical --export-bom, --export-wires, --export-cables, --export-nets and --export-wiring on industrial.qet. Its load drops from 5.51 s to 5.31 s (median of 6). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01L6MRq2Ach1ogvnGcbuqNLr --- sources/dataBase/projectdatabase.cpp | 41 ++++++++++++++++++++++++++++ sources/dataBase/projectdatabase.h | 17 ++++++++++++ sources/qetproject.cpp | 6 ++++ 3 files changed, 64 insertions(+) diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 1549f6ce0..df686fe12 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -53,6 +53,7 @@ projectDataBase::projectDataBase(QETProject *project, QObject *parent) : }); connect(m_project, &QETProject::projectDiagramsOrderChanged, [this]() { + m_content_changed = true; for (auto diagram : m_project->diagrams()) { m_diagram_order_changed.bindValue(":pos", m_project->folioIndex(diagram)+1); @@ -85,14 +86,42 @@ projectDataBase::~projectDataBase() */ void projectDataBase::updateDB() { + //A bulk operation is in progress and updates the database itself once + //it is done : rebuilding now would only be thrown away by that final + //rebuild. @see setUpdateBlocked(). + if (m_update_blocked) { + return; + } + + //Nothing in the project has changed since the last rebuild, so + //repopulating would insert exactly the rows that are already there. + //The signal is still emitted : callers and models rely on it to + //refresh, and what they read back is unchanged either way. + if (!m_content_changed) + { + emit dataBaseUpdated(); + return; + } + populateDiagramTable(); populateDiagramInfoTable(); populateElementTable(); populateElementInfoTable(); populateConductorTable(); + m_content_changed = false; + emit dataBaseUpdated(); } +/** + @brief projectDataBase::setUpdateBlocked + @param blocked : whether updateDB() should skip the full rebuild +*/ +void projectDataBase::setUpdateBlocked(bool blocked) +{ + m_update_blocked = blocked; +} + /** @brief projectDataBase::project @return the project of this database @@ -158,6 +187,7 @@ int projectDataBase::excludedConductorCount() const */ void projectDataBase::addElement(Element *element) { + m_content_changed = true; if (!element || !element->diagram()) { qDebug() << "projectDataBase::addElement: null element or diagram"; return; @@ -182,6 +212,7 @@ void projectDataBase::addElement(Element *element) */ void projectDataBase::removeElement(Element *element) { + m_content_changed = true; m_remove_element_query.bindValue(":uuid", element->uuid().toString()); if(!m_remove_element_query.exec()) { qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError(); @@ -196,6 +227,7 @@ void projectDataBase::removeElement(Element *element) */ void projectDataBase::elementInfoChanged(Element *element) { + m_content_changed = true; auto hash = elementInfoToString(element); for (auto str : QETInformation::elementInfoKeys()) { m_update_element_query.bindValue(":" + str, hash.value(str)); @@ -210,6 +242,7 @@ void projectDataBase::elementInfoChanged(Element *element) void projectDataBase::elementInfoChanged(QList elements) { + m_content_changed = true; this->blockSignals(true); //Block signal for not emit dataBaseUpdated at //each call of the method elementInfoChanged(Element *element) @@ -226,6 +259,7 @@ void projectDataBase::elementInfoChanged(QList elements) void projectDataBase::addDiagram(Diagram *diagram) { + m_content_changed = true; m_insert_diagram_query.bindValue(":uuid", diagram->uuid().toString()); m_insert_diagram_query.bindValue(":pos", m_project->folioIndex(diagram)+1); if(!m_insert_diagram_query.exec()) { @@ -254,6 +288,7 @@ void projectDataBase::addDiagram(Diagram *diagram) void projectDataBase::removeDiagram(Diagram *diagram) { + m_content_changed = true; const QString uuid_str = diagram->uuid().toString(); //Order matters: element_info and terminal are scoped through a @@ -309,6 +344,7 @@ void projectDataBase::removeDiagram(Diagram *diagram) void projectDataBase::diagramInfoChanged(Diagram *diagram) { + m_content_changed = true; bindDiagramInfoValues(m_update_diagram_info_query, diagram); if (!m_update_diagram_info_query.exec()) { @@ -320,6 +356,7 @@ void projectDataBase::diagramInfoChanged(Diagram *diagram) void projectDataBase::diagramOrderChanged() { + m_content_changed = true; } /** @@ -328,6 +365,7 @@ void projectDataBase::diagramOrderChanged() */ void projectDataBase::addConductor(Conductor *conductor) { + m_content_changed = true; if (!conductor || !conductor->diagram()) { qDebug() << "projectDataBase::addConductor: null conductor or diagram"; return; @@ -361,6 +399,7 @@ void projectDataBase::addConductor(Conductor *conductor) */ void projectDataBase::removeConductor(Conductor *conductor) { + m_content_changed = true; m_remove_conductor_query.bindValue(":uuid", conductor->uuid().toString()); if (!m_remove_conductor_query.exec()) { qDebug() << "projectDataBase::removeConductor delete error : " << m_remove_conductor_query.lastError(); @@ -381,6 +420,7 @@ void projectDataBase::removeConductor(Conductor *conductor) */ void projectDataBase::updateConductor(Conductor *conductor) { + m_content_changed = true; if (!conductor) { return; } @@ -423,6 +463,7 @@ void projectDataBase::watchConductor(Conductor *conductor) */ void projectDataBase::conductorPropertiesChanged() { + m_content_changed = true; if (auto *conductor = qobject_cast(sender())) { updateConductor(conductor); } diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 7964bb093..2ce9266bb 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -47,6 +47,17 @@ class projectDataBase : public QObject virtual ~projectDataBase() override; void updateDB(); + /** + Suppress the full rebuild performed by updateDB(). + + While blocked, updateDB() returns immediately instead of + repopulating every table. Meant for bulk operations -- notably + loading a project, where each table model re-queries the + database as it is built and would otherwise trigger one + complete rebuild of it. The caller unblocks and calls + updateDB() once when done; @see QETProject::readProjectXml(). + */ + void setUpdateBlocked(bool blocked); QETProject *project() const; QSqlQuery newQuery(const QString &query = QString()); QSqlDatabase database() const {return m_data_base;} @@ -97,6 +108,12 @@ class projectDataBase : public QObject private: QPointer m_project; + bool m_update_blocked = false; + //Starts true : the database is empty until the first rebuild. + //Set by every method of this class that writes rows, cleared by + //updateDB(). Callers reach the database from outside only through + //newQuery(), and every such call site reads. + bool m_content_changed = true; QSqlDatabase m_data_base; QSqlQuery m_insert_elements_query, m_insert_element_info_query, diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index be8df383f..18429779d 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -1547,6 +1547,11 @@ void QETProject::readProjectXml(QDomDocument &xml_project) } m_data_base.blockSignals(true); + //Blocking the signals is not enough : every table model built below + //re-queries the database, and each of those queries used to trigger a + //complete rebuild of it. The content being loaded is the same for all + //of them, so a single rebuild once everything is in place is enough. + m_data_base.setUpdateBlocked(true); //Load the project-wide properties readProjectPropertiesXml(xml_project); @@ -1586,6 +1591,7 @@ void QETProject::readProjectXml(QDomDocument &xml_project) const qint64 refresh_ms = phase_timer.restart(); m_data_base.blockSignals(false); + m_data_base.setUpdateBlocked(false); m_data_base.updateDB(); const qint64 database_ms = phase_timer.elapsed();