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 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-13 07:05:27 +13:00
parent 798010cc83
commit 4104bb8d53
+78 -12
View File
@@ -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<QString, QString> &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<QString, QString> fewestAttributes(QHash<QString, QString> attributes)
{
const ConductorProperties full = propertiesFrom(attributes);
QStringList names = attributes.keys();
names.sort();
for (const QString &name : std::as_const(names)) {
QHash<QString, QString> 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<std::pair<QUuid, QString>> &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<QString, QHash<QString, QString>> 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<QString, QString> 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<QUuid, QHash<QString, QString>> 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<QUuid> 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<QString, QString> &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());