diff --git a/sources/container/qetcontainer.h b/sources/container/qetcontainer.h
index 8daa78424..33740306f 100644
--- a/sources/container/qetcontainer.h
+++ b/sources/container/qetcontainer.h
@@ -33,7 +33,7 @@
manifest.xml format and oldest format allowed to read it
project.xml the project, each part replaced by
- project.sqlite the engineering data (format 2,
+ project.sqlite the engineering data (format 2 on,
QetContainerDb): title-block fields, symbol
information, links, wires
folios/.xml one per folio
@@ -56,7 +56,12 @@ namespace QetContainer
using Pictures = QHash;
/// The format written; files of a later format are refused by join()
/// unless their min-reader allows this one.
- constexpr int Format = 3;
+ constexpr int Format = 4;
+ /// 3: the database's rows keyed by the items' uuids, true/false as
+ /// 1/0, its PRAGMA user_version the format.
+ /// 4: what QElectroTech computes left out of the folios
+ /// (dropComputed()), the tables written without keys or indexes.
+ /// Formats 2 and 3 are still read.
QList split(const QDomDocument &project,
const QString &writer = QString(),
@@ -75,7 +80,7 @@ namespace QetContainer
/// The file join() left the picture of @p image in, or an empty string
QString pictureFile(const QDomElement &image);
- /// Take what QElectroTech computes out of a folio (format 3)
+ /// Take what QElectroTech computes out of a folio (format 4)
void dropComputed(QDomElement diagram, bool terminal_lists);
/// Parse @p xml as a .qet is read (QETProject::openFile)
bool parse(const QByteArray &xml, QDomDocument *document, QString *error = nullptr);
diff --git a/sources/container/qetcontainerdb.cpp b/sources/container/qetcontainerdb.cpp
index 85349e9fc..aea1b4768 100644
--- a/sources/container/qetcontainerdb.cpp
+++ b/sources/container/qetcontainerdb.cpp
@@ -17,13 +17,15 @@
*/
#include "qetcontainerdb.h"
-#include
+#include "qetcontainer.h"
+#include
#include
#include
#include
#include
#include
+#include
#include
#include
@@ -31,9 +33,9 @@
namespace {
const QString KeyAttribute = QStringLiteral("qetz-key");
+const QString FolioAttribute = QStringLiteral("qetz-folio");
//A folio written as it was read (stage D4): its part
const QString KeptPartAttribute = QStringLiteral("qetz-kept-part");
-const QString FolioAttribute = QStringLiteral("qetz-folio");
//What a moved block leaves where it was. Removing it instead would leave
//the whitespace before and after it side by side, read back as one text
//node: the parent would come back a child short.
@@ -63,39 +65,63 @@ const QStringList FolioData = {
QStringLiteral("title"), QStringLiteral("author"), QStringLiteral("date"),
QStringLiteral("folio"), QStringLiteral("indexrev"), QStringLiteral("filename"),
QStringLiteral("locmach"), QStringLiteral("plant"), QStringLiteral("auto_page_num")};
+ //From format 3 on an item's uuid is its row's key, not a column of its
+ //own: it stays in the folio file, where it says which row is the item's
const QStringList ElementData = {
- QStringLiteral("uuid"), QStringLiteral("type"), QStringLiteral("prefix"),
- QStringLiteral("freezeLabel")};
+ QStringLiteral("type"), QStringLiteral("prefix"), QStringLiteral("freezeLabel")};
const QStringList ConductorData = {
- QStringLiteral("uuid"), QStringLiteral("element1"), QStringLiteral("terminal1"),
+ QStringLiteral("element1"), QStringLiteral("terminal1"),
QStringLiteral("element2"), QStringLiteral("terminal2"), QStringLiteral("num"),
QStringLiteral("formula"), QStringLiteral("function"), QStringLiteral("tension_protocol"),
QStringLiteral("conductor_color"), QStringLiteral("conductor_section"),
QStringLiteral("cable"), QStringLiteral("bus"), QStringLiteral("freezeLabel")};
+ //Attributes held as 0 or 1 (format 3 on). A value QElectroTech does not
+ //write, "yes" say, stays in the folio file as it was.
+const QStringList BooleanData = {QStringLiteral("freezeLabel")};
+
QString columns(const QStringList &names)
{
QStringList quoted;
- for (const QString &n : names) quoted << QStringLiteral("\"%1\" TEXT").arg(n);
+ for (const QString &n : names)
+ quoted << QStringLiteral("\"%1\" %2").arg(n, BooleanData.contains(n) ? QStringLiteral("INTEGER")
+ : QStringLiteral("TEXT"));
return quoted.join(QStringLiteral(", "));
}
+ //@p text as attribute @p name is stored, or invalid if it cannot be
+QVariant stored(const QString &name, const QString &text)
+{
+ if (!BooleanData.contains(name)) return text;
+ if (text == QLatin1String("true")) return 1;
+ if (text == QLatin1String("false")) return 0;
+ return {};
+}
+
+ //A stored value as attribute @p name writes it
+QString attributeText(const QString &name, const QVariant &value)
+{
+ if (BooleanData.contains(name) && value.typeId() == QMetaType::Int)
+ return value.toInt() ? QStringLiteral("true") : QStringLiteral("false");
+ return value.toString();
+}
+
QString schema()
{
return QStringLiteral(
"CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT);"
"CREATE TABLE project_property (ord INTEGER PRIMARY KEY, name TEXT, value TEXT, attrs TEXT);"
- "CREATE TABLE folio (id TEXT PRIMARY KEY, pos INTEGER NOT NULL UNIQUE, %1,"
+ "CREATE TABLE folio (uuid TEXT PRIMARY KEY, pos INTEGER NOT NULL UNIQUE, %1,"
" props_at INTEGER, props_attrs TEXT);"
- "CREATE TABLE folio_property (folio_id TEXT NOT NULL REFERENCES folio, ord INTEGER NOT NULL,"
- " name TEXT, value TEXT, attrs TEXT, PRIMARY KEY (folio_id, ord));"
- "CREATE TABLE element (key TEXT PRIMARY KEY, folio_id TEXT NOT NULL REFERENCES folio, %2,"
+ "CREATE TABLE folio_property (folio_uuid TEXT NOT NULL REFERENCES folio, ord INTEGER NOT NULL,"
+ " name TEXT, value TEXT, attrs TEXT, PRIMARY KEY (folio_uuid, ord));"
+ "CREATE TABLE element (uuid TEXT PRIMARY KEY, folio_uuid TEXT NOT NULL REFERENCES folio, %2,"
" info_at INTEGER, info_attrs TEXT, links_at INTEGER, links_attrs TEXT);"
- "CREATE TABLE element_info (element_key TEXT NOT NULL REFERENCES element, ord INTEGER NOT NULL,"
- " name TEXT, value TEXT, attrs TEXT, PRIMARY KEY (element_key, ord));"
- "CREATE TABLE link (element_key TEXT NOT NULL REFERENCES element, ord INTEGER NOT NULL,"
- " linked_uuid TEXT, attrs TEXT, PRIMARY KEY (element_key, ord));"
- "CREATE TABLE conductor (key TEXT PRIMARY KEY, folio_id TEXT NOT NULL REFERENCES folio, %3);"
+ "CREATE TABLE element_info (element_uuid TEXT NOT NULL REFERENCES element, ord INTEGER NOT NULL,"
+ " name TEXT, value TEXT, show INTEGER, attrs TEXT, PRIMARY KEY (element_uuid, ord));"
+ "CREATE TABLE link (element_uuid TEXT NOT NULL REFERENCES element, ord INTEGER NOT NULL,"
+ " linked_uuid TEXT, attrs TEXT, PRIMARY KEY (element_uuid, ord));"
+ "CREATE TABLE conductor (uuid TEXT PRIMARY KEY, folio_uuid TEXT NOT NULL REFERENCES folio, %3);"
"CREATE TABLE terminal_strip (uuid TEXT PRIMARY KEY, ord INTEGER NOT NULL UNIQUE,"
" installation TEXT, location TEXT, name TEXT, comment TEXT, description TEXT);"
"CREATE TABLE strip_terminal (strip_uuid TEXT NOT NULL REFERENCES terminal_strip,"
@@ -108,9 +134,9 @@ QString schema()
.arg(columns(FolioData), columns(ElementData), columns(ConductorData));
}
- //The tables of a document's engineering data. From format 3 on they are
- //named doc_, beside the project database's own tables in the same
- //file (stage D3)
+ //The tables of a document's engineering data. While the project
+ //database's own tables were in the same file they were named
+ //doc_ (stage D3)
const QStringList DocTables = {
QStringLiteral("strip_bridge_terminal"), QStringLiteral("strip_bridge"),
QStringLiteral("strip_terminal"), QStringLiteral("terminal_strip"),
@@ -119,13 +145,15 @@ const QStringList DocTables = {
QStringLiteral("conductor"), QStringLiteral("meta")};
//Owns an sqlite3 handle; its SQL names the document's tables by their
- //format 2 names, given their prefix here
+ //plain names, given their prefix here, and its columns as the format of
+ //the database read names them
class Database
{
public:
~Database() { if (m_db) sqlite3_close(m_db); }
sqlite3 *m_db = nullptr;
QString m_prefix;
+ int m_format = QetContainer::Format;
QString sql(const QString &text) const {
if (m_prefix.isEmpty()) return text;
static const QRegularExpression names(QStringLiteral("(? children(const QDomElement &parent, const QString &tag)
return list;
}
- //uuid when present and unique among @p items, else prefix + count
-QStringList keys(const QList &items, const QString &prefix)
+ //The key of each of @p items: its uuid, when it has one no other of
+ //@p items has and @p reserved does not hold, so the uuid the folio file
+ //already holds says which row is the item's (@p own true). Else (F100:
+ //symbols copied with their uuid) a uuid made from the item itself --
+ //the same on every save, never from where the item is -- kept in the
+ //folio file as qetz-key. @p reserved: the keys of rows copied from
+ //another database (a folio kept as it was)
+QStringList keys(const QList &items, QList *own,
+ const QSet &reserved = QSet())
{
+ static const QUuid key_namespace(QStringLiteral("{3f0c6c1e-5b8e-4b8a-9d55-6b2f5f0f9a41}"));
QHash seen;
for (const QDomElement &e : items)
- if (e.hasAttribute(QStringLiteral("uuid"))) ++seen[e.attribute(QStringLiteral("uuid"))];
+ if (!QUuid(e.attribute(QStringLiteral("uuid"))).isNull()) ++seen[e.attribute(QStringLiteral("uuid"))];
+ QSet taken(seen.keyBegin(), seen.keyEnd());
+ taken.unite(reserved);
+ QHash twins;
QStringList out;
- int made_up = 0;
for (const QDomElement &e : items) {
const QString uuid = e.attribute(QStringLiteral("uuid"));
- out << ((!uuid.isEmpty() && seen.value(uuid) == 1)
- ? uuid : prefix + QString::number(++made_up));
+ if (seen.value(uuid) == 1 && !reserved.contains(uuid)) {
+ out << uuid;
+ *own << true;
+ continue;
+ }
+ QString text;
+ QTextStream stream(&text);
+ e.save(stream, -1);
+ stream.flush();
+ //Items alike to the byte: how many came before tells them apart
+ const int twin = twins[text]++;
+ QString key;
+ for (int salt = 0 ; key.isEmpty() || taken.contains(key) ; ++salt)
+ key = QUuid::createUuidV5(key_namespace, QStringLiteral("%1\n%2\n%3").arg(text).arg(twin).arg(salt))
+ .toString();
+ taken.insert(key);
+ out << key;
+ *own << false;
}
return out;
}
-}
-namespace {
- //@p database opened read only in @p db, its tables checked to be plain
- //tables: a file is untrusted
//Every table of @p db written again without its keys and indexes, in
//the order of its rows, and the file compacted: the file a .qetz holds
- //is read whole, its lookups indexed once read (openDatabase(),
- //projectDataBase::fillFromIndex()), and keys and indexes of text uuids
- //were half of it (stage D3b)
+ //is read whole, its lookups indexed once read (openDatabase()), and keys
+ //and indexes of text uuids were half of it (stage D3b)
bool plainTables(sqlite3 *db)
{
QStringList tables;
@@ -498,6 +563,8 @@ bool plainTables(sqlite3 *db)
return ok && run(QStringLiteral("VACUUM"));
}
+ //@p database opened in @p db, its tables checked to be plain tables and
+ //its format one this reads: a file is untrusted
bool openDatabase(Database &db, const QByteArray &database, QString *error)
{
if (sqlite3_open(":memory:", &db.m_db) != SQLITE_OK) {
@@ -516,7 +583,8 @@ bool openDatabase(Database &db, const QByteArray &database, QString *error)
//A file is untrusted: nothing in its schema may run, and every name
//read must be a plain table.
db.exec(QStringLiteral("PRAGMA trusted_schema = OFF"));
- //Format 3 names the document's tables doc_, format 2 not
+ //Beside the project database's own tables the document's are
+ //named doc_
{
Statement doc(db, QStringLiteral("SELECT count(*) FROM sqlite_master WHERE name = 'doc_meta'"));
if (doc.next() && doc.value(0).toInt() > 0) db.m_prefix = QStringLiteral("doc_");
@@ -530,11 +598,24 @@ bool openDatabase(Database &db, const QByteArray &database, QString *error)
setError(error, QStringLiteral("the project database has no table %1").arg(QLatin1String(table)));
return false;
}
+ }
+ {
+ Statement format(db, QStringLiteral("SELECT value FROM meta WHERE key = 'format'"));
+ const QString text = format.next() ? format.value(0).toString() : QString();
+ db.m_format = text.toInt();
+ if (db.m_format < 2 || db.m_format > QetContainer::Format) {
+ setError(error, QStringLiteral("the project database is of format %1, this reads 2 to %2")
+ .arg(text).arg(QetContainer::Format));
+ return false;
+ }
}
//What the folios are read by: a file holds no index (plainTables())
- for (const char *index : {"folio(id)", "folio_property(folio_id)", "element(key)", "element(folio_id)",
- "element_info(element_key)", "link(element_key)", "conductor(key)", "meta(key)"}) {
- const QString on = QString::fromLatin1(index);
+ const QStringList indexes = {
+ QStringLiteral("folio(%1)").arg(db.folioKey()), QStringLiteral("folio_property(%1)").arg(db.folioRef()),
+ QStringLiteral("element(%1)").arg(db.itemKey()), QStringLiteral("element(%1)").arg(db.folioRef()),
+ QStringLiteral("element_info(%1)").arg(db.elementRef()), QStringLiteral("link(%1)").arg(db.elementRef()),
+ QStringLiteral("conductor(%1)").arg(db.itemKey()), QStringLiteral("meta(key)")};
+ for (const QString &on : indexes) {
const QString name = QStringLiteral("qetz_read_") + QString(on).replace(QLatin1Char('('), QLatin1Char('_')).remove(QLatin1Char(')'));
if (!db.exec(QStringLiteral("CREATE INDEX IF NOT EXISTS %1 ON %2").arg(name, on))) {
setError(error, QStringLiteral("the project database cannot be read: %1").arg(db.error()));
@@ -546,29 +627,46 @@ bool openDatabase(Database &db, const QByteArray &database, QString *error)
}
QDomElement entryElement(QDomDocument &document, const QVariant &name_attr, const QString &name,
- const QVariant &value, const QVariant &attrs, const QString &default_tag)
+ const QVariant &value, const QVariant &attrs, const QString &default_tag,
+ const QVariant &show = QVariant())
{
const QJsonObject o = QJsonDocument::fromJson(attrs.toString().toUtf8()).object();
QDomElement e = document.createElement(o.value(QStringLiteral("tag")).toString(default_tag));
if (!name_attr.isNull()) e.setAttribute(name, name_attr.toString());
+ if (!show.isNull()) e.setAttribute(QStringLiteral("show"), show.toString());
setAttributesJson(e, attrs);
if (!value.isNull()) e.appendChild(document.createTextNode(value.toString()));
return e;
}
- //The engineering data of the folio @p d (its qetz-folio id) back from @p db
+ //An item's key: the one it was given, else (format 3 on) its uuid
+QString keyOf(const Database &db, QDomElement e, const QString &attribute)
+{
+ if (e.hasAttribute(attribute)) {
+ const QString key = e.attribute(attribute);
+ e.removeAttribute(attribute);
+ return key;
+ }
+ return db.v2() ? QString() : e.attribute(QStringLiteral("uuid"));
+}
+
+QString quoted(const QStringList &names)
+{
+ return QStringLiteral("\"") + names.join(QStringLiteral("\", \"")) + QStringLiteral("\"");
+}
+
+ //The engineering data of the folio @p d back from @p db
bool restoreDiagram(Database &db, QDomElement d, QString *error)
{
QDomDocument document = d.ownerDocument();
auto entry = [&document](const QVariant &name_attr, const QString &name, const QVariant &value,
- const QVariant &attrs, const QString &default_tag) {
- return entryElement(document, name_attr, name, value, attrs, default_tag);
+ const QVariant &attrs, const QString &default_tag, const QVariant &show = QVariant()) {
+ return entryElement(document, name_attr, name, value, attrs, default_tag, show);
};
- const QString id = d.attribute(FolioAttribute);
+ const QString id = keyOf(db, d, FolioAttribute);
if (id.isEmpty()) return true;
- d.removeAttribute(FolioAttribute);
- Statement f(db, QStringLiteral("SELECT %1, props_at, props_attrs FROM folio WHERE id = ?")
- .arg(QStringLiteral("\"") + FolioData.join(QStringLiteral("\", \"")) + QStringLiteral("\"")));
+ Statement f(db, QStringLiteral("SELECT %1, props_at, props_attrs FROM folio WHERE %2 = ?")
+ .arg(quoted(FolioData), db.folioKey()));
f.bind({id});
if (!f.next()) { setError(error, QStringLiteral("folio %1 is not in the database").arg(id)); return false; }
QVariantList values;
@@ -577,7 +675,8 @@ bool restoreDiagram(Database &db, QDomElement d, QString *error)
if (!values.at(FolioData.size()).isNull()) {
QDomElement props = document.createElement(QStringLiteral("properties"));
setAttributesJson(props, values.at(FolioData.size() + 1));
- Statement p(db, QStringLiteral("SELECT name, value, attrs FROM folio_property WHERE folio_id = ? ORDER BY ord"));
+ Statement p(db, QStringLiteral("SELECT name, value, attrs FROM folio_property WHERE %1 = ? ORDER BY ord")
+ .arg(db.folioRef()));
p.bind({id});
while (p.next())
props.appendChild(entry(p.value(0), QStringLiteral("name"), p.value(1), p.value(2),
@@ -589,34 +688,37 @@ bool restoreDiagram(Database &db, QDomElement d, QString *error)
}
}
+ const QStringList element_data = db.elementData();
+ const QStringList conductor_data = db.conductorData();
for (const QDomElement &list : children(d, QStringLiteral("elements"))) {
for (QDomElement e : children(list, QStringLiteral("element"))) {
- if (!e.hasAttribute(KeyAttribute)) continue;
- const QString key = e.attribute(KeyAttribute);
- e.removeAttribute(KeyAttribute);
- Statement s(db, QStringLiteral("SELECT %1, info_at, info_attrs, links_at, links_attrs FROM element WHERE key = ?")
- .arg(QStringLiteral("\"") + ElementData.join(QStringLiteral("\", \"")) + QStringLiteral("\"")));
+ const QString key = keyOf(db, e, KeyAttribute);
+ if (key.isEmpty()) continue;
+ Statement s(db, QStringLiteral("SELECT %1, info_at, info_attrs, links_at, links_attrs FROM element WHERE %2 = ?")
+ .arg(quoted(element_data), db.itemKey()));
s.bind({key});
if (!s.next()) { setError(error, QStringLiteral("symbol %1 is not in the database").arg(key)); return false; }
QVariantList v;
- for (int i = 0 ; i < ElementData.size() + 4 ; ++i) v << s.value(i);
- put(e, ElementData, v, 0);
- const int n = ElementData.size();
+ for (int i = 0 ; i < element_data.size() + 4 ; ++i) v << s.value(i);
+ put(e, element_data, v, 0);
+ const int n = element_data.size();
QDomElement info, links;
if (!v.at(n).isNull()) {
info = document.createElement(QStringLiteral("elementInformations"));
setAttributesJson(info, v.at(n + 1));
- Statement i(db, QStringLiteral("SELECT name, value, attrs FROM element_info WHERE element_key = ? ORDER BY ord"));
+ Statement i(db, QStringLiteral("SELECT name, value, attrs, %1 FROM element_info WHERE %2 = ? ORDER BY ord")
+ .arg(db.showColumn(), db.elementRef()));
i.bind({key});
while (i.next())
info.appendChild(entry(i.value(0), QStringLiteral("name"), i.value(1), i.value(2),
- QStringLiteral("elementInformation")));
+ QStringLiteral("elementInformation"), i.value(3)));
restoreWhitespace(info, v.at(n + 1));
}
if (!v.at(n + 2).isNull()) {
links = document.createElement(QStringLiteral("links_uuids"));
setAttributesJson(links, v.at(n + 3));
- Statement l(db, QStringLiteral("SELECT linked_uuid, attrs FROM link WHERE element_key = ? ORDER BY ord"));
+ Statement l(db, QStringLiteral("SELECT linked_uuid, attrs FROM link WHERE %1 = ? ORDER BY ord")
+ .arg(db.elementRef()));
l.bind({key});
while (l.next())
links.appendChild(entry(l.value(0), QStringLiteral("uuid"), QVariant(), l.value(1),
@@ -632,16 +734,15 @@ bool restoreDiagram(Database &db, QDomElement d, QString *error)
}
for (const QDomElement &list : children(d, QStringLiteral("conductors"))) {
for (QDomElement c : children(list, QStringLiteral("conductor"))) {
- if (!c.hasAttribute(KeyAttribute)) continue;
- const QString key = c.attribute(KeyAttribute);
- c.removeAttribute(KeyAttribute);
- Statement s(db, QStringLiteral("SELECT %1 FROM conductor WHERE key = ?")
- .arg(QStringLiteral("\"") + ConductorData.join(QStringLiteral("\", \"")) + QStringLiteral("\"")));
+ const QString key = keyOf(db, c, KeyAttribute);
+ if (key.isEmpty()) continue;
+ Statement s(db, QStringLiteral("SELECT %1 FROM conductor WHERE %2 = ?")
+ .arg(quoted(conductor_data), db.itemKey()));
s.bind({key});
if (!s.next()) { setError(error, QStringLiteral("wire %1 is not in the database").arg(key)); return false; }
QVariantList v;
- for (int i = 0 ; i < ConductorData.size() ; ++i) v << s.value(i);
- put(c, ConductorData, v, 0);
+ for (int i = 0 ; i < conductor_data.size() ; ++i) v << s.value(i);
+ put(c, conductor_data, v, 0);
}
}
return true;
@@ -658,14 +759,14 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
const QByteArray &kept_from)
{
Database db;
- db.m_prefix = QStringLiteral("doc_");
if (sqlite3_open(":memory:", &db.m_db) != SQLITE_OK) {
setError(error, QStringLiteral("cannot create the project database"));
return {};
}
//The project database's own tables first, when given: one file
- //holds both (stage D3)
+ //holds both (stage D3), the document's named doc_
if (!index.isEmpty()) {
+ db.m_prefix = QStringLiteral("doc_");
unsigned char *copy = static_cast(sqlite3_malloc64(sqlite3_uint64(index.size())));
if (!copy) { setError(error, QStringLiteral("out of memory")); return {}; }
memcpy(copy, index.constData(), size_t(index.size()));
@@ -680,7 +781,7 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
return {};
}
//The database of the .qetz this project was read from, for the folios
- //kept as they were (stage D4)
+ //kept as they were (stage D4): of this format (currentFormat())
if (!kept_from.isEmpty()) {
unsigned char *copy = static_cast(sqlite3_malloc64(sqlite3_uint64(kept_from.size())));
if (!copy) { setError(error, QStringLiteral("out of memory")); return {}; }
@@ -708,13 +809,14 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
Statement folio(db, QStringLiteral("INSERT INTO folio VALUES (?,?,%1,?,?)").arg(placeholders(FolioData.size())));
Statement folio_property(db, QStringLiteral("INSERT INTO folio_property VALUES (?,?,?,?,?)"));
Statement element(db, QStringLiteral("INSERT INTO element VALUES (?,?,%1,?,?,?,?)").arg(placeholders(ElementData.size())));
- Statement element_info(db, QStringLiteral("INSERT INTO element_info VALUES (?,?,?,?,?)"));
+ Statement element_info(db, QStringLiteral("INSERT INTO element_info VALUES (?,?,?,?,?,?)"));
Statement link(db, QStringLiteral("INSERT INTO link VALUES (?,?,?,?)"));
Statement conductor(db, QStringLiteral("INSERT INTO conductor VALUES (?,?,%1)").arg(placeholders(ConductorData.size())));
bool ok = meta.ok() && project_property.ok() && folio.ok() && folio_property.ok()
&& element.ok() && element_info.ok() && link.ok() && conductor.ok();
- ok = ok && meta.run({QStringLiteral("format"), QStringLiteral("3")});
+ ok = ok && meta.run({QStringLiteral("format"), QString::number(QetContainer::Format)})
+ && db.exec(QStringLiteral("PRAGMA user_version = %1").arg(QetContainer::Format));
// of a block, as rows of @p rows (with @p prefix values)
auto moveProperties = [&](QDomElement block, Statement &rows, const QVariantList &prefix) {
@@ -724,7 +826,24 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
simpleValue(p, &value);
ok = ok && rows.run(QVariantList(prefix) << ord++ << (p.hasAttribute(QStringLiteral("name"))
? QVariant(p.attribute(QStringLiteral("name"))) : QVariant())
- << value << attributesJson(p, QStringLiteral("name"), true));
+ << value << attributesJson(p, {QStringLiteral("name")}, QStringLiteral("property")));
+ }
+ };
+ //The entries of @p block, as the rows of the
+ //symbol @p key
+ auto moveInformation = [&](const QDomElement &block, const QString &key) {
+ int ord = 0;
+ for (QDomElement i : children(block, QStringLiteral("elementInformation"))) {
+ QVariant value;
+ simpleValue(i, &value);
+ const QString show = i.attribute(QStringLiteral("show"));
+ const bool show_stored = show == QLatin1String("0") || show == QLatin1String("1");
+ QStringList kept{QStringLiteral("name")};
+ if (show_stored) kept << QStringLiteral("show");
+ ok = ok && element_info.run({key, ord++, i.hasAttribute(QStringLiteral("name"))
+ ? QVariant(i.attribute(QStringLiteral("name"))) : QVariant(),
+ value, show_stored ? QVariant(show.toInt()) : QVariant(),
+ attributesJson(i, kept, QStringLiteral("elementInformation"))});
}
};
@@ -739,7 +858,7 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
simpleValue(p, &value);
ok = ok && project_property.run({ord++, p.hasAttribute(QStringLiteral("name"))
? QVariant(p.attribute(QStringLiteral("name"))) : QVariant(),
- value, attributesJson(p, QStringLiteral("name"), true)});
+ value, attributesJson(p, {QStringLiteral("name")}, QStringLiteral("property"))});
}
leaveMarker(project_props);
}
@@ -752,87 +871,78 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
for (const QDomElement &list : children(d, QStringLiteral("conductors")))
all_conductors += children(list, QStringLiteral("conductor"));
}
- const QStringList element_keys = keys(all_elements, QStringLiteral("e"));
- const QStringList conductor_keys = keys(all_conductors, QStringLiteral("c"));
+ //The keys of the rows the folios kept as they were bring, which no
+ //other item may have
+ QSet kept_elements, kept_conductors;
+ for (const QDomElement &d : diagrams) {
+ if (!d.hasAttribute(KeptPartAttribute)) continue;
+ Statement e(db, QStringLiteral("SELECT uuid FROM old.element WHERE folio_uuid = ?"));
+ e.bind({d.attribute(QStringLiteral("uuid"))});
+ while (e.next()) kept_elements.insert(e.value(0).toString());
+ Statement c(db, QStringLiteral("SELECT uuid FROM old.conductor WHERE folio_uuid = ?"));
+ c.bind({d.attribute(QStringLiteral("uuid"))});
+ while (c.next()) kept_conductors.insert(c.value(0).toString());
+ }
+ QList folio_own, element_own, conductor_own;
+ const QStringList folio_keys = keys(diagrams, &folio_own);
+ const QStringList element_keys = keys(all_elements, &element_own, kept_elements);
+ const QStringList conductor_keys = keys(all_conductors, &conductor_own, kept_conductors);
int e_index = 0, c_index = 0;
- //A folio's id is its uuid when every folio has one of its own, so
- //that a folio kept as it was keeps its rows (format 3, D4)
- bool uuid_ids = true;
- {
- QSet seen;
- for (const QDomElement &d : diagrams) {
- const QString uuid = d.attribute(QStringLiteral("uuid"));
- if (QUuid(uuid).isNull() || seen.contains(uuid)) { uuid_ids = false; break; }
- seen.insert(uuid);
- }
- }
- if (uuid_ids) {
- ok = ok && meta.run({QStringLiteral("folio_ids"), QStringLiteral("uuid")});
- }
-
int pos = 0;
for (QDomElement d : diagrams)
{
+ const QString id = folio_keys.at(pos);
+ const bool own_id = folio_own.at(pos);
++pos;
- const QString id = uuid_ids ? d.attribute(QStringLiteral("uuid")) : QStringLiteral("f%1").arg(pos);
- //Kept as it was: its rows from the database it was read from
+ //Kept as it was: its rows from the database it was read from,
+ //under its uuid
if (d.hasAttribute(KeptPartAttribute)) {
- if (kept_from.isEmpty() || !uuid_ids) {
+ if (kept_from.isEmpty() || !own_id) {
setError(error, QStringLiteral("a folio kept as it was, without its database"));
return {};
}
- Statement copy(db, QStringLiteral("INSERT INTO folio SELECT id, ?, %1, props_at, props_attrs "
- "FROM old.folio WHERE id = ?")
- .arg(QStringLiteral("\"") + FolioData.join(QStringLiteral("\", \"")) + QStringLiteral("\"")));
+ Statement copy(db, QStringLiteral("INSERT INTO folio SELECT uuid, ?, %1, props_at, props_attrs "
+ "FROM old.folio WHERE uuid = ?").arg(quoted(FolioData)));
ok = ok && copy.run({pos, id}) && sqlite3_changes(db.m_db) == 1;
for (const QString &sql : {
- QStringLiteral("INSERT INTO folio_property SELECT * FROM old.folio_property WHERE folio_id = ? ORDER BY rowid"),
- QStringLiteral("INSERT INTO element SELECT * FROM old.element WHERE folio_id = ? ORDER BY rowid"),
- QStringLiteral("INSERT INTO element_info SELECT * FROM old.element_info WHERE element_key IN "
- "(SELECT key FROM old.element WHERE folio_id = ?) ORDER BY rowid"),
- QStringLiteral("INSERT INTO link SELECT * FROM old.link WHERE element_key IN "
- "(SELECT key FROM old.element WHERE folio_id = ?) ORDER BY rowid"),
- QStringLiteral("INSERT INTO conductor SELECT * FROM old.conductor WHERE folio_id = ? ORDER BY rowid")}) {
+ QStringLiteral("INSERT INTO folio_property SELECT * FROM old.folio_property WHERE folio_uuid = ? ORDER BY rowid"),
+ QStringLiteral("INSERT INTO element SELECT * FROM old.element WHERE folio_uuid = ? ORDER BY rowid"),
+ QStringLiteral("INSERT INTO element_info SELECT * FROM old.element_info WHERE element_uuid IN "
+ "(SELECT uuid FROM old.element WHERE folio_uuid = ?) ORDER BY rowid"),
+ QStringLiteral("INSERT INTO link SELECT * FROM old.link WHERE element_uuid IN "
+ "(SELECT uuid FROM old.element WHERE folio_uuid = ?) ORDER BY rowid"),
+ QStringLiteral("INSERT INTO conductor SELECT * FROM old.conductor WHERE folio_uuid = ? ORDER BY rowid")}) {
Statement rows(db, sql);
ok = ok && rows.run({id});
}
//Its symbols whose information changed since (QETProject::toXml()):
//their information rows written as a symbol's block writes them
for (const QDomElement &edited : children(d, QStringLiteral("qetz-edited-element"))) {
- Statement find(db, QStringLiteral("SELECT key FROM element WHERE folio_id = ? AND uuid = ? AND info_at IS NOT NULL"));
+ Statement find(db, QStringLiteral("SELECT uuid FROM element WHERE folio_uuid = ? AND uuid = ? AND info_at IS NOT NULL"));
find.bind({id, edited.attribute(QStringLiteral("uuid"))});
if (!find.next()) {
setError(error, QStringLiteral("a symbol changed on folio %1 kept as it was is not in its database").arg(id));
return {};
}
const QString key = find.value(0).toString();
- Statement drop(db, QStringLiteral("DELETE FROM element_info WHERE element_key = ?"));
+ Statement drop(db, QStringLiteral("DELETE FROM element_info WHERE element_uuid = ?"));
ok = ok && drop.run({key});
- int ord = 0;
- for (QDomElement i : children(edited.firstChildElement(QStringLiteral("elementInformations")),
- QStringLiteral("elementInformation"))) {
- QVariant value;
- simpleValue(i, &value);
- ok = ok && element_info.run({key, ord++, i.hasAttribute(QStringLiteral("name"))
- ? QVariant(i.attribute(QStringLiteral("name"))) : QVariant(),
- value, attributesJson(i, QStringLiteral("name"), true)});
- }
+ moveInformation(edited.firstChildElement(QStringLiteral("elementInformations")), key);
}
//Its wires whose properties changed since: their columns
for (const QDomElement &edited : children(d, QStringLiteral("qetz-edited-conductor"))) {
QStringList set;
QVariantList values;
for (const QString &column : ConductorData) {
- if (column == QLatin1String("uuid") || column.startsWith(QLatin1String("element"))
- || column.startsWith(QLatin1String("terminal")) || column == QLatin1String("freezeLabel")
- || !edited.hasAttribute(column))
+ if (column.startsWith(QLatin1String("element")) || column.startsWith(QLatin1String("terminal"))
+ || BooleanData.contains(column) || !edited.hasAttribute(column))
continue;
set << QStringLiteral("\"%1\" = ?").arg(column);
values << edited.attribute(column);
}
values << id << edited.attribute(QStringLiteral("uuid"));
- Statement update(db, QStringLiteral("UPDATE conductor SET %1 WHERE folio_id = ? AND uuid = ?")
+ Statement update(db, QStringLiteral("UPDATE conductor SET %1 WHERE folio_uuid = ? AND uuid = ?")
.arg(set.join(QStringLiteral(", "))));
ok = ok && !set.isEmpty() && update.run(values) && sqlite3_changes(db.m_db) == 1;
}
@@ -853,11 +963,12 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
moveProperties(props, folio_property, {id});
leaveMarker(props);
}
- d.setAttribute(FolioAttribute, id);
+ if (!own_id) d.setAttribute(FolioAttribute, id);
for (const QDomElement &list : children(d, QStringLiteral("elements"))) {
for (QDomElement e : children(list, QStringLiteral("element"))) {
- const QString key = element_keys.at(e_index++);
+ const QString key = element_keys.at(e_index);
+ const bool own_key = element_own.at(e_index++);
QVariantList erow{key, id};
erow += take(e, ElementData);
QDomElement info = e.firstChildElement(QStringLiteral("elementInformations"));
@@ -871,35 +982,28 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
<< (links_at < 0 ? QVariant() : QVariant(links_at))
<< (links_at < 0 ? QVariant() : blockJson(links));
ok = ok && element.run(erow);
- if (info_at >= 0) {
- int ord = 0;
- for (QDomElement i : children(info, QStringLiteral("elementInformation"))) {
- QVariant value;
- simpleValue(i, &value);
- ok = ok && element_info.run({key, ord++, i.hasAttribute(QStringLiteral("name"))
- ? QVariant(i.attribute(QStringLiteral("name"))) : QVariant(),
- value, attributesJson(i, QStringLiteral("name"), true)});
- }
- }
+ if (info_at >= 0)
+ moveInformation(info, key);
if (links_at >= 0) {
int ord = 0;
for (QDomElement l : children(links, QStringLiteral("link_uuid")))
ok = ok && link.run({key, ord++, l.hasAttribute(QStringLiteral("uuid"))
? QVariant(l.attribute(QStringLiteral("uuid"))) : QVariant(),
- attributesJson(l, QStringLiteral("uuid"), true)});
+ attributesJson(l, {QStringLiteral("uuid")}, QStringLiteral("link_uuid"))});
}
if (info_at >= 0) leaveMarker(info);
if (links_at >= 0) leaveMarker(links);
- e.setAttribute(KeyAttribute, key);
+ if (!own_key) e.setAttribute(KeyAttribute, key);
}
}
for (const QDomElement &list : children(d, QStringLiteral("conductors"))) {
for (QDomElement c : children(list, QStringLiteral("conductor"))) {
- const QString key = conductor_keys.at(c_index++);
+ const QString key = conductor_keys.at(c_index);
+ const bool own_key = conductor_own.at(c_index++);
QVariantList crow{key, id};
crow += take(c, ConductorData);
ok = ok && conductor.run(crow);
- c.setAttribute(KeyAttribute, key);
+ if (!own_key) c.setAttribute(KeyAttribute, key);
}
}
}
@@ -955,16 +1059,6 @@ bool QetContainerDb::restore(QDomElement project, const QByteArray &database, QS
if (!openDatabase(db, database, error))
return false;
- auto entry = [&document](const QVariant &name_attr, const QString &name, const QVariant &value,
- const QVariant &attrs, const QString &default_tag) {
- const QJsonObject o = QJsonDocument::fromJson(attrs.toString().toUtf8()).object();
- QDomElement e = document.createElement(o.value(QStringLiteral("tag")).toString(default_tag));
- if (!name_attr.isNull()) e.setAttribute(name, name_attr.toString());
- setAttributesJson(e, attrs);
- if (!value.isNull()) e.appendChild(document.createTextNode(value.toString()));
- return e;
- };
-
//Project properties
QHash meta;
{
@@ -976,8 +1070,8 @@ bool QetContainerDb::restore(QDomElement project, const QByteArray &database, QS
setAttributesJson(props, meta.value(QStringLiteral("props_attrs")));
Statement s(db, QStringLiteral("SELECT name, value, attrs FROM project_property ORDER BY ord"));
while (s.next())
- props.appendChild(entry(s.value(0), QStringLiteral("name"), s.value(1), s.value(2),
- QStringLiteral("property")));
+ props.appendChild(entryElement(document, s.value(0), QStringLiteral("name"), s.value(1), s.value(2),
+ QStringLiteral("property")));
restoreWhitespace(props, meta.value(QStringLiteral("props_attrs")));
if (!replaceMarker(project, props)) {
setError(error, QStringLiteral("the project's properties have no place"));
@@ -1064,16 +1158,13 @@ bool QetContainerDb::FolioReader::restore(QDomElement diagram, QString *error)
}
/**
- @brief QetContainerDb::foliosByUuid
- @return true if @p database, a format 3 project.sqlite, names its
- folios by their uuid: a folio written as it was read keeps its rows
- under it (stage D4)
+ @brief QetContainerDb::currentFormat
+ @return true if @p database, a project.sqlite, is of the format this
+ writes: a folio written as it was read keeps its rows, copied as they
+ are (stage D4)
*/
-bool QetContainerDb::foliosByUuid(const QByteArray &database)
+bool QetContainerDb::currentFormat(const QByteArray &database)
{
Database db;
- if (!openDatabase(db, database, nullptr) || db.m_prefix.isEmpty())
- return false;
- Statement s(db, QStringLiteral("SELECT value FROM meta WHERE key = 'folio_ids'"));
- return s.next() && s.value(0).toString() == QLatin1String("uuid");
+ return openDatabase(db, database, nullptr) && db.m_format == QetContainer::Format;
}
diff --git a/sources/container/qetcontainerdb.h b/sources/container/qetcontainerdb.h
index 1b616acf2..5e34f307d 100644
--- a/sources/container/qetcontainerdb.h
+++ b/sources/container/qetcontainerdb.h
@@ -32,8 +32,10 @@
drawn. extract() takes them out of the document into a database,
restore() puts them back, so that restore(extract(d)) is d.
- Each moved item leaves its key in the document (qetz-key, qetz-folio):
- its uuid where that is unique, else a made-up one. A block of a shape
+ A row's key is its item's uuid, which stays in the folio file. An item
+ without a uuid of its own -- none, or one another item shares -- gets a
+ uuid made from what it holds, left in the document as qetz-key or
+ qetz-folio. A block of a shape
this does not model -- a value holding elements, say -- stays in the
document, so nothing can be lost by moving.
*/
@@ -55,8 +57,8 @@ namespace QetContainerDb
struct Private;
std::unique_ptr d;
};
- /// true if @p database names its folios by their uuid (format 3)
- bool foliosByUuid(const QByteArray &database);
+ /// true if @p database is of the format this writes
+ bool currentFormat(const QByteArray &database);
}
#endif // QETCONTAINERDB_H
diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp
index 31c603181..e5600445c 100644
--- a/sources/qetproject.cpp
+++ b/sources/qetproject.cpp
@@ -3485,7 +3485,7 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
m_kept_parts_trusted = m_opened_from_index && unloadedFolioCount() > 0
&& m_project_qet_version == QetVersion::currentVersion()
&& m_data_base.rowsFromDocument()
- && QetContainerDb::foliosByUuid(m_container_files.value(QStringLiteral("project.sqlite")));
+ && QetContainerDb::currentFormat(m_container_files.value(QStringLiteral("project.sqlite")));
m_kept_folios_stale = false;
m_edited_since_open = false;
if (m_kept_folios_trusted || m_kept_parts_trusted)
diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt
index 2418a9ef0..18f285472 100644
--- a/tests/qttest/CMakeLists.txt
+++ b/tests/qttest/CMakeLists.txt
@@ -1253,4 +1253,5 @@ add_test(NAME tst_qetcontainer COMMAND tst_qetcontainer)
target_include_directories(tst_qetcontainer PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_qetcontainer PRIVATE Qt::Test Qt::Xml SQLite3::SQLite3)
target_compile_definitions(tst_qetcontainer PRIVATE
- "QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
+ "QET_EXAMPLES_DIR=\"${QET_DIR}/examples\""
+ "QET_FIXTURES_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/fixtures\"")
diff --git a/tests/qttest/fixtures/qetz_format2.qetz b/tests/qttest/fixtures/qetz_format2.qetz
new file mode 100644
index 000000000..fdf8d066b
Binary files /dev/null and b/tests/qttest/fixtures/qetz_format2.qetz differ
diff --git a/tests/qttest/lazy_folios_probe.cpp b/tests/qttest/lazy_folios_probe.cpp
index 630dca6db..9e0d0e1cb 100644
--- a/tests/qttest/lazy_folios_probe.cpp
+++ b/tests/qttest/lazy_folios_probe.cpp
@@ -288,7 +288,7 @@ static QStringList savedLinks(const QString &qetz, const QString &path)
db.setDatabaseName(db_path);
check(db.open(), "a .qetz's database opened");
QSqlQuery query(db);
- check(query.exec(QStringLiteral("SELECT element_key, linked_uuid FROM doc_link ORDER BY element_key, ord")),
+ check(query.exec(QStringLiteral("SELECT element_uuid, linked_uuid FROM doc_link ORDER BY element_uuid, ord")),
"saved links read");
while (query.next())
links[query.value(0).toString()] << query.value(1).toString();
diff --git a/tests/qttest/tst_qetcontainer.cpp b/tests/qttest/tst_qetcontainer.cpp
index 93676ead3..dd818596a 100644
--- a/tests/qttest/tst_qetcontainer.cpp
+++ b/tests/qttest/tst_qetcontainer.cpp
@@ -81,6 +81,57 @@ class tst_qetcontainer : public QObject
return copy;
}
+ static QByteArray entry(const QList &entries, const QString &name)
+ {
+ for (const QetZip::Entry &e : entries)
+ if (e.name == name) return e.data;
+ return {};
+ }
+
+ static QByteArray folioFiles(const QList &entries)
+ {
+ QByteArray all;
+ for (const QetZip::Entry &e : entries)
+ if (e.name.startsWith(QStringLiteral("folios/"))) all += e.data;
+ return all;
+ }
+
+ // The first column of the first row @p sql gives, from @p database
+ static QString query(const QByteArray &database, const char *sql)
+ {
+ sqlite3 *db = nullptr;
+ sqlite3_open(":memory:", &db);
+ unsigned char *copy = static_cast(sqlite3_malloc64(sqlite3_uint64(database.size())));
+ memcpy(copy, database.constData(), size_t(database.size()));
+ sqlite3_deserialize(db, "main", copy, database.size(), database.size(), SQLITE_DESERIALIZE_FREEONCLOSE);
+ sqlite3_stmt *st = nullptr;
+ QString out;
+ if (sqlite3_prepare_v2(db, sql, -1, &st, nullptr) == SQLITE_OK && sqlite3_step(st) == SQLITE_ROW)
+ out = QString::fromUtf8(reinterpret_cast(sqlite3_column_text(st, 0)));
+ sqlite3_finalize(st);
+ sqlite3_close(db);
+ return out;
+ }
+
+ // grafcet.qet as QElectroTech saves it: every folio, symbol and wire
+ // with a uuid of its own, which the examples' wires and folios lack
+ static QDomDocument savedProject()
+ {
+ QList entries;
+ QDomDocument project;
+ QetZip::fromBytes(read(QStringLiteral(QET_FIXTURES_DIR "/qetz_format2.qetz")), &entries);
+ QetContainer::join(entries, &project);
+ return project;
+ }
+
+ static QString backDifference(const QDomDocument &original, const QList &entries)
+ {
+ QDomDocument back;
+ QString error;
+ if (!QetContainer::join(entries, &back, &error)) return error;
+ return difference(withoutComputed(original).documentElement(), back.documentElement(), QString());
+ }
+
private slots:
void sameDocumentBack_data()
{
@@ -185,14 +236,14 @@ private slots:
return n;
};
const QDomNodeList diagrams = original.elementsByTagName(QStringLiteral("diagram"));
- QCOMPARE(count("doc_folio"), int(diagrams.size()));
- QCOMPARE(count("doc_element"), int(original.elementsByTagName(QStringLiteral("element")).size())
+ QCOMPARE(count("folio"), int(diagrams.size()));
+ QCOMPARE(count("element"), int(original.elementsByTagName(QStringLiteral("element")).size())
- int(original.elementsByTagName(QStringLiteral("collection")).at(0).toElement()
.elementsByTagName(QStringLiteral("element")).size()));
- QCOMPARE(count("doc_element_info"), int(original.elementsByTagName(QStringLiteral("elementInformation")).size()));
- QCOMPARE(count("doc_link"), int(original.elementsByTagName(QStringLiteral("link_uuid")).size()));
- QCOMPARE(count("doc_conductor"), int(original.elementsByTagName(QStringLiteral("conductor")).size()));
- QVERIFY(count("doc_folio_property") + count("doc_project_property") > 0);
+ QCOMPARE(count("element_info"), int(original.elementsByTagName(QStringLiteral("elementInformation")).size()));
+ QCOMPARE(count("link"), int(original.elementsByTagName(QStringLiteral("link_uuid")).size()));
+ QCOMPARE(count("conductor"), int(original.elementsByTagName(QStringLiteral("conductor")).size()));
+ QVERIFY(count("folio_property") + count("doc_project_property") > 0);
sqlite3_close(db);
}
@@ -388,8 +439,8 @@ private slots:
memcpy(copy, e.data.constData(), size_t(e.data.size()));
sqlite3_deserialize(db, "main", copy, e.data.size(), e.data.size(),
SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE);
- QCOMPARE(sqlite3_exec(db, "ALTER TABLE doc_element RENAME TO element_data;"
- "CREATE VIEW doc_element AS SELECT * FROM element_data;",
+ QCOMPARE(sqlite3_exec(db, "ALTER TABLE element RENAME TO element_data;"
+ "CREATE VIEW element AS SELECT * FROM element_data;",
nullptr, nullptr, nullptr), SQLITE_OK);
sqlite3_int64 size = 0;
unsigned char *bytes = sqlite3_serialize(db, "main", &size, 0);
@@ -403,6 +454,104 @@ private slots:
QVERIFY2(error.contains(QStringLiteral("element")), qPrintable(error));
}
+ // A row's key is its item's uuid, which the folio file keeps: no key
+ // made up from where an item is (format 2 had f1, f2...), no key
+ // attribute in the folio files of a project as QElectroTech saves it,
+ // true/false held as 1/0, and the format in the database's
+ // user_version.
+ void keysAreTheUuids()
+ {
+ const QDomDocument original = savedProject();
+ original.elementsByTagName(QStringLiteral("conductor")).at(0).toElement()
+ .setAttribute(QStringLiteral("freezeLabel"), QStringLiteral("true"));
+ QString error;
+ const QList entries = QetContainer::split(original, QString(), &error);
+ QVERIFY2(!entries.isEmpty(), qPrintable(error));
+ const QByteArray database = entry(entries, QStringLiteral("project.sqlite"));
+ const QByteArray folios = folioFiles(entries);
+ QVERIFY(!folios.contains("qetz-key"));
+ QVERIFY(!folios.contains("qetz-folio"));
+ QCOMPARE(query(database, "PRAGMA user_version"), QString::number(QetContainer::Format));
+ const QString first = original.elementsByTagName(QStringLiteral("diagram")).at(0).toElement()
+ .attribute(QStringLiteral("uuid"));
+ QCOMPARE(query(database, "SELECT uuid FROM folio WHERE pos = 1"), first);
+ QCOMPARE(query(database, "SELECT count(*) FROM folio WHERE uuid NOT LIKE '{%}'"), QStringLiteral("0"));
+ QCOMPARE(query(database, "SELECT count(*) FROM element WHERE uuid NOT LIKE '{%}'"), QStringLiteral("0"));
+ QCOMPARE(query(database, "SELECT count(*) FROM conductor WHERE uuid NOT LIKE '{%}'"), QStringLiteral("0"));
+ QCOMPARE(query(database, "SELECT count(*) FROM element WHERE typeof(freezeLabel) <> 'integer'"),
+ QStringLiteral("0"));
+ QCOMPARE(query(database, "SELECT count(*) FROM conductor WHERE typeof(freezeLabel) <> 'integer'"),
+ QStringLiteral("0"));
+ QCOMPARE(query(database, "SELECT sum(freezeLabel) FROM conductor"), QStringLiteral("1"));
+ const QString d = backDifference(original, entries);
+ QVERIFY2(d.isEmpty(), qPrintable(d));
+ QCOMPARE(query(database, "SELECT count(*) FROM element_info WHERE typeof(show) <> 'integer'"),
+ QStringLiteral("0"));
+ QCOMPARE(query(database, "SELECT count(*) FROM element_info WHERE attrs LIKE '%\"tag\"%'"),
+ QStringLiteral("0"));
+ }
+
+ // Two symbols sharing a uuid (F100) each get a key made from what they
+ // hold, the same on every save, and come back as they were.
+ void sharedUuidKeyed()
+ {
+ QDomDocument original = savedProject();
+ const QDomNodeList elements = original.elementsByTagName(QStringLiteral("element"));
+ QDomElement a, b;
+ for (int i = 0 ; i < elements.size() && b.isNull() ; ++i) {
+ const QDomElement e = elements.at(i).toElement();
+ if (e.parentNode().nodeName() != QLatin1String("elements")) continue;
+ if (a.isNull()) a = e; else b = e;
+ }
+ QVERIFY(!b.isNull());
+ b.setAttribute(QStringLiteral("uuid"), a.attribute(QStringLiteral("uuid")));
+ QString error;
+ const QList once = QetContainer::split(original, QString(), &error);
+ const QList twice = QetContainer::split(original, QString(), &error);
+ QVERIFY2(!once.isEmpty(), qPrintable(error));
+ QCOMPARE(folioFiles(once).count("qetz-key"), 2);
+ QCOMPARE(entry(once, QStringLiteral("project.sqlite")), entry(twice, QStringLiteral("project.sqlite")));
+ const QString d = backDifference(original, once);
+ QVERIFY2(d.isEmpty(), qPrintable(d));
+ }
+
+ // A true/false attribute QElectroTech would not write stays in the
+ // folio file as it was.
+ void oddBooleanKept()
+ {
+ QDomDocument original;
+ QVERIFY(QetContainer::parse(read(QStringLiteral(QET_EXAMPLES_DIR "/perceuse.qet")), &original));
+ const QDomNodeList conductors = original.elementsByTagName(QStringLiteral("conductor"));
+ QVERIFY(conductors.size() > 0);
+ conductors.at(0).toElement().setAttribute(QStringLiteral("freezeLabel"), QStringLiteral("yes"));
+ QString error;
+ const QList entries = QetContainer::split(original, QString(), &error);
+ QVERIFY2(!entries.isEmpty(), qPrintable(error));
+ QVERIFY(folioFiles(entries).contains("freezeLabel=\"yes\""));
+ const QString d = backDifference(original, entries);
+ QVERIFY2(d.isEmpty(), qPrintable(d));
+ }
+
+ // A format 2 file, as QElectroTech 0.200.1 development builds wrote it
+ // (grafcet.qet saved as .qetz), still opens, and saved again as the
+ // current format comes back the same.
+ void format2Read()
+ {
+ QList entries;
+ QString error;
+ QVERIFY2(QetZip::fromBytes(read(QStringLiteral(QET_FIXTURES_DIR "/qetz_format2.qetz")), &entries, &error),
+ qPrintable(error));
+ QVERIFY(entry(entries, QStringLiteral("manifest.xml")).contains("format=\"2\""));
+ QDomDocument project;
+ QVERIFY2(QetContainer::join(entries, &project, &error), qPrintable(error));
+ QVERIFY(project.toString().contains(QStringLiteral(" resaved = QetContainer::split(project, QString(), &error);
+ QVERIFY2(!resaved.isEmpty(), qPrintable(error));
+ const QString d = backDifference(project, resaved);
+ QVERIFY2(d.isEmpty(), qPrintable(d));
+ }
+
void notAProjectRefused()
{
QDomDocument back;