From 4104bb8d537b02fb67b39d58200fdb5814e74507 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Tue, 13 Oct 2026 07:05:27 +1300 Subject: [PATCH] Write only the wire attributes reading needs in a .qetz (stage D3b) A wire's properties are one row per attribute in the project database, 45 071 rows for f2099's 2 139 wires. The database a .qetz holds now keeps only the attributes reading would not give anyway: each is left out in turn when the properties read without it are the same (ConductorProperties ::fromXml(), its defaults); rows by wire and name, so the file does not depend on memory order. Reading writes every attribute back into the table, so queries and saves see what they saw before. f2099: 9 387 rows, the file 3.60 MB -> 3.22 MB (5.14 MB before D3b); industrial.qet 0.71 -> 0.61 MB. Gates with the master merge and the wire edits by uuid: ctest 99/99 lazily, 98/99 with every folio built (modal_quit_regression, the race fixed next); hostile .qetz all refused; 342 corpus projects save, export, round trip and re-save byte-stable. Co-Authored-By: Claude Opus 5.5 --- sources/dataBase/projectdatabase.cpp | 90 ++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 12 deletions(-) diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 32085c9b3..6dd82ea82 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -931,6 +931,38 @@ QStringList indexTables(const QSqlDatabase &db) } } +namespace { + //A wire's properties read from @p attributes, as a saved wire's are +ConductorProperties propertiesFrom(const QHash &attributes) +{ + QDomDocument scratch; + QDomElement e = scratch.createElement(QStringLiteral("conductor")); + for (auto it = attributes.cbegin() ; it != attributes.cend() ; ++it) + e.setAttribute(it.key(), it.value()); + ConductorProperties properties; + properties.fromXml(e); + return properties; +} + + //@p attributes without those reading them would give anyway: each + //left out in turn when the properties read without it are the same + //(stage D3b) +QHash fewestAttributes(QHash attributes) +{ + const ConductorProperties full = propertiesFrom(attributes); + QStringList names = attributes.keys(); + names.sort(); + for (const QString &name : std::as_const(names)) { + QHash without = attributes; + without.remove(name); + const ConductorProperties read = propertiesFrom(without); + if (read == full && read.attributes() == full.attributes()) + attributes = without; + } + return attributes; +} +} + /** @brief projectDataBase::indexFile This database written to an SQLite file, for a .qetz to be opened from @@ -1011,6 +1043,29 @@ QByteArray projectDataBase::indexFile(const QList> &fo query.addBindValue(table.folio.toString()); query.addBindValue(table.previous.isNull() ? QVariant() : QVariant(table.previous.toString())); ok = ok && query.exec(); + } + //A wire's properties: only the attributes reading them needs, + //by wire and name (fillFromIndex() writes the others back) + { + QMap> wires; + QSqlQuery rows(file); + rows.exec(QStringLiteral("SELECT conductor_uuid, name, value FROM conductor_properties")); + while (rows.next()) + wires[rows.value(0).toString()].insert(rows.value(1).toString(), rows.value(2).toString()); + ok = ok && query.exec(QStringLiteral("DELETE FROM conductor_properties")); + query.prepare(QStringLiteral("INSERT INTO conductor_properties (conductor_uuid, name, value) " + "VALUES (?, ?, ?)")); + for (auto wire = wires.cbegin() ; wire != wires.cend() ; ++wire) { + const QHash fewest = fewestAttributes(wire.value()); + QStringList names = fewest.keys(); + names.sort(); + for (const QString &name : std::as_const(names)) { + query.addBindValue(wire.key()); + query.addBindValue(name); + query.addBindValue(fewest.value(name)); + ok = ok && query.exec(); + } + } } ok = file.commit() && ok; ok = query.exec(QStringLiteral("VACUUM")) && ok; @@ -1164,18 +1219,29 @@ bool projectDataBase::fillFromIndex(const QByteArray &index, query.value(3).toInt() != 0); } QHash> attributes; - query.exec(QStringLiteral("SELECT conductor_uuid, name, value FROM conductor_properties")); - while (query.next()) - attributes[QUuid(query.value(0).toString())].insert(query.value(1).toString(), - query.value(2).toString()); - QDomDocument scratch; - for (auto it = attributes.cbegin() ; it != attributes.cend() ; ++it) { - QDomElement e = scratch.createElement(QStringLiteral("conductor")); - for (auto a = it.value().cbegin() ; a != it.value().cend() ; ++a) - e.setAttribute(a.key(), a.value()); - ConductorProperties properties; - properties.fromXml(e); - m_prefilled_conductors.insert(it.key(), properties); + QList wire_order; + query.exec(QStringLiteral("SELECT conductor_uuid, name, value FROM conductor_properties ORDER BY rowid")); + while (query.next()) { + const QUuid wire(query.value(0).toString()); + if (!attributes.contains(wire)) wire_order << wire; + attributes[wire].insert(query.value(1).toString(), query.value(2).toString()); + } + //A file may hold only the attributes reading needs (indexFile()): + //the table gets every attribute a saved wire carries, as before + query.exec(QStringLiteral("DELETE FROM conductor_properties")); + query.prepare(QStringLiteral("INSERT INTO conductor_properties (conductor_uuid, name, value) VALUES (?, ?, ?)")); + for (const QUuid &wire : std::as_const(wire_order)) { + const ConductorProperties properties = propertiesFrom(attributes.value(wire)); + m_prefilled_conductors.insert(wire, properties); + QHash &full = attributes[wire]; + full.clear(); + for (const auto &attribute : properties.attributes()) { + full.insert(attribute.first, attribute.second); + query.addBindValue(wire.toString()); + query.addBindValue(attribute.first); + query.addBindValue(attribute.second); + query.exec(); + } } for (auto it = m_prefilled_elements.cbegin() ; it != m_prefilled_elements.cend() ; ++it) m_element_information.insert(it.key(), it.value());