diff --git a/sources/container/qetcontainer.cpp b/sources/container/qetcontainer.cpp index 15ffe995d..f6659ebbc 100644 --- a/sources/container/qetcontainer.cpp +++ b/sources/container/qetcontainer.cpp @@ -169,16 +169,18 @@ bool QetContainer::isZip(const QByteArray &content) /** @brief QetContainer::readDocument The project document held in @p content: unzipped and joined for a - .qetz, parsed for a .qet. + .qetz, parsed for a .qet. @p format, if any: the .qetz's format, 0 + for a .qet. */ bool QetContainer::readDocument(const QByteArray &content, QDomDocument *document, - QString *error, Pictures *pictures) + QString *error, Pictures *pictures, int *format) { + if (format) *format = 0; if (!isZip(content)) return parse(content, document, error); QList entries; return QetZip::fromBytes(content, &entries, error) - && join(entries, document, error, nullptr, pictures); + && join(entries, document, error, nullptr, pictures, format); } QString QetContainer::pictureFile(const QDomElement &image) @@ -277,6 +279,29 @@ QList QetContainer::split(const QDomDocument &project, const QStr //A folio written as it was read (, //stage D4) keeps its part and, through //@p kept_from's project.sqlite, its rows + //A symbol changed on a folio kept as it was gets its information + //rows written anew: its part must have the place they go back to + for (const QDomElement &diagram : childElements(root, QStringLiteral("diagram"))) + { + const QList edited = childElements(diagram, QStringLiteral("qetz-edited-element")); + if (edited.isEmpty()) continue; + QDomDocument part; + if (!parse(kept_from.value(diagram.attribute(QStringLiteral("qetz-kept-part"))), &part, error)) + return {}; + QSet placed; + const QDomNodeList elements = part.elementsByTagName(QStringLiteral("element")); + for (int i = 0 ; i < elements.size() ; ++i) { + const QDomElement element = elements.at(i).toElement(); + for (const QDomElement &marker : childElements(element, QStringLiteral("qetz-db"))) + if (marker.attribute(QStringLiteral("block")) == QLatin1String("elementInformations")) + placed.insert(element.attribute(QStringLiteral("uuid"))); + } + for (const QDomElement &symbol : edited) + if (!placed.contains(symbol.attribute(QStringLiteral("uuid")))) { + setError(error, QStringLiteral("a symbol changed on a folio kept as it was has no place for its information")); + return {}; + } + } const QByteArray database = QetContainerDb::extract( root, error, kept_from.value(QStringLiteral("project.sqlite"))); if (database.isEmpty()) @@ -391,10 +416,11 @@ QList QetContainer::split(const QDomDocument &project, const QStr nothing names are ignored, so a later format may add some. With @p pictures, the pictures stay out of the document: see the namespace. With @p folio_files, the part each folio was read from, in - order. + order. @p format, if any: the format the file says it is, above Format + for a later one this may read (its min-reader allows it). */ bool QetContainer::join(const QList &entries, QDomDocument *project, QString *error, - QStringList *folio_files, Pictures *pictures) + QStringList *folio_files, Pictures *pictures, int *format) { QHash files; for (const QetZip::Entry &entry : entries) @@ -415,6 +441,8 @@ bool QetContainer::join(const QList &entries, QDomDocument *proje .arg(Format)); return false; } + if (format) + *format = manifest.documentElement().attribute(QStringLiteral("format")).toInt(); if (!parse(files.value(QStringLiteral("project.xml")), project, error)) return false; diff --git a/sources/container/qetcontainer.h b/sources/container/qetcontainer.h index 54d75a693..241716950 100644 --- a/sources/container/qetcontainer.h +++ b/sources/container/qetcontainer.h @@ -57,12 +57,15 @@ namespace QetContainer /// unless their min-reader allows this one. 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. + /// 1/0, its PRAGMA user_version the format, the symbols' type left + /// in the folio files, no column for where a block sat. /// 4: what QElectroTech computes left out of the folios /// (dropComputed()), the tables written without keys or indexes. /// project.sqlite holds the engineering data once: QElectroTech's /// own project database is built from it when the project opens. - /// Formats 2 and 3 are still read. + /// Formats 2 and 3 are still read; a later format whose min-reader + /// allows this one is read too, and said so through join()'s + /// @p format. QList split(const QDomDocument &project, const QString &writer = QString(), @@ -70,7 +73,7 @@ namespace QetContainer const QHash &kept_from = QHash()); bool join(const QList &entries, QDomDocument *project, QString *error = nullptr, QStringList *folio_files = nullptr, - Pictures *pictures = nullptr); + Pictures *pictures = nullptr, int *format = nullptr); /// The file join() left the picture of @p image in, or an empty string QString pictureFile(const QDomElement &image); @@ -84,7 +87,7 @@ namespace QetContainer bool isZip(const QByteArray &content); /// The project document in @p content, a .qet or a .qetz bool readDocument(const QByteArray &content, QDomDocument *document, - QString *error = nullptr, Pictures *pictures = nullptr); + QString *error = nullptr, Pictures *pictures = nullptr, int *format = nullptr); } #endif // QETCONTAINER_H diff --git a/sources/container/qetcontainerdb.cpp b/sources/container/qetcontainerdb.cpp index cdc73b41e..8ddf4518a 100644 --- a/sources/container/qetcontainerdb.cpp +++ b/sources/container/qetcontainerdb.cpp @@ -60,6 +60,15 @@ bool replaceMarker(QDomElement parent, const QDomElement &block) return false; } + //true if @p parent holds the marker of a block @p tag moved out: where + //the block goes back, and that it was there at all +bool hasMarker(const QDomElement &parent, const QString &tag) +{ + for (QDomElement m = parent.firstChildElement(MarkerTag) ; !m.isNull() ; m = m.nextSiblingElement(MarkerTag)) + if (m.attribute(MarkerBlock) == tag) return true; + return false; +} + const QStringList FolioData = { QStringLiteral("title"), QStringLiteral("author"), QStringLiteral("date"), QStringLiteral("folio"), QStringLiteral("indexrev"), QStringLiteral("filename"), @@ -116,11 +125,11 @@ QString schema() "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 (uuid TEXT PRIMARY KEY, pos INTEGER NOT NULL UNIQUE, %1," - " props_at INTEGER, props_attrs TEXT);" + " props_attrs TEXT);" "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);" + " info_attrs TEXT, links_attrs TEXT);" "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," @@ -320,15 +329,6 @@ bool listBlock(const QDomElement &block, const QString &entry) return true; } -int childIndex(const QDomNode &child) -{ - int i = 0; - for (QDomNode n = child.parentNode().firstChild() ; !n.isNull() ; n = n.nextSibling(), ++i) - if (n == child) return i; - return -1; -} - - //The attributes @p names of @p e, taken out of it; one that cannot be //stored stays, its value null QVariantList take(QDomElement e, const QStringList &names) @@ -587,9 +587,11 @@ bool openDatabase(Database &db, const QByteArray &database, QString *error) 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)); + //A later format is read when the manifest's min-reader allows it + //(QetContainer::join()): its writer kept what this reads + if (db.m_format < 2) { + setError(error, QStringLiteral("the project database is of format %1, this reads format 2 on") + .arg(text)); return false; } } @@ -666,23 +668,25 @@ bool restoreDiagram(Database &db, QDomElement d, UsedRows &used, QString *error) const QString id = keyOf(db, d, FolioAttribute); if (id.isEmpty()) return true; if (!used.use(used.folios, id, QStringLiteral("folio"), error)) return false; - Statement f(db, QStringLiteral("SELECT %1, props_at, props_attrs FROM folio WHERE %2 = ?") + Statement f(db, QStringLiteral("SELECT %1, 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; - for (int i = 0 ; i < FolioData.size() + 2 ; ++i) values << f.value(i); + for (int i = 0 ; i < FolioData.size() + 1 ; ++i) values << f.value(i); put(d, FolioData, values, 0); - if (!values.at(FolioData.size()).isNull()) { + //A moved block goes back where its marker is: the marker says it + //was there + if (hasMarker(d, QStringLiteral("properties"))) { QDomElement props = document.createElement(QStringLiteral("properties")); - setAttributesJson(props, values.at(FolioData.size() + 1)); + setAttributesJson(props, values.at(FolioData.size())); 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), QStringLiteral("property"))); - restoreWhitespace(props, values.at(FolioData.size() + 1)); + restoreWhitespace(props, values.at(FolioData.size())); if (!replaceMarker(d, props)) { setError(error, QStringLiteral("folio %1's properties have no place").arg(id)); return false; @@ -696,40 +700,40 @@ bool restoreDiagram(Database &db, QDomElement d, UsedRows &used, QString *error) const QString key = keyOf(db, e, KeyAttribute); if (key.isEmpty()) continue; if (!used.use(used.elements, key, QStringLiteral("symbol"), error)) return false; - Statement s(db, QStringLiteral("SELECT %1, info_at, info_attrs, links_at, links_attrs, %2 FROM element WHERE %3 = ?") + Statement s(db, QStringLiteral("SELECT %1, info_attrs, links_attrs, %2 FROM element WHERE %3 = ?") .arg(quoted(element_data), db.folioRef(), db.itemKey())); s.bind({key}); if (!s.next()) { setError(error, QStringLiteral("symbol %1 is not in the database").arg(key)); return false; } - if (s.value(element_data.size() + 4).toString() != id) { + if (s.value(element_data.size() + 2).toString() != id) { setError(error, QStringLiteral("symbol %1 is on folio %2, the database says another").arg(key, id)); return false; } QVariantList v; - for (int i = 0 ; i < element_data.size() + 4 ; ++i) v << s.value(i); + for (int i = 0 ; i < element_data.size() + 2 ; ++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()) { + if (hasMarker(e, QStringLiteral("elementInformations"))) { info = document.createElement(QStringLiteral("elementInformations")); - setAttributesJson(info, v.at(n + 1)); + setAttributesJson(info, v.at(n)); 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"), i.value(3))); - restoreWhitespace(info, v.at(n + 1)); + restoreWhitespace(info, v.at(n)); } - if (!v.at(n + 2).isNull()) { + if (hasMarker(e, QStringLiteral("links_uuids"))) { links = document.createElement(QStringLiteral("links_uuids")); - setAttributesJson(links, v.at(n + 3)); + setAttributesJson(links, v.at(n + 1)); 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), QStringLiteral("link_uuid"))); - restoreWhitespace(links, v.at(n + 3)); + restoreWhitespace(links, v.at(n + 1)); } if ((!info.isNull() && !replaceMarker(e, info)) || (!links.isNull() && !replaceMarker(e, links))) { @@ -829,9 +833,9 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB Statement meta(db, QStringLiteral("INSERT INTO meta VALUES (?,?)")); Statement project_property(db, QStringLiteral("INSERT INTO project_property VALUES (?,?,?,?)")); - Statement folio(db, QStringLiteral("INSERT INTO folio VALUES (?,?,%1,?,?)").arg(placeholders(FolioData.size()))); + 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(db, QStringLiteral("INSERT INTO element VALUES (?,?,%1,?,?)").arg(placeholders(ElementData.size()))); 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()))); @@ -873,7 +877,6 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB //Project properties const QDomElement project_props = project.firstChildElement(QStringLiteral("properties")); if (!project_props.isNull() && listBlock(project_props, QStringLiteral("property"))) { - ok = ok && meta.run({QStringLiteral("props_at"), QString::number(childIndex(project_props))}); ok = ok && meta.run({QStringLiteral("props_attrs"), blockJson(project_props)}); int ord = 0; for (QDomElement p : children(project_props, QStringLiteral("property"))) { @@ -925,7 +928,7 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB setError(error, QStringLiteral("a folio kept as it was, without its database")); return {}; } - Statement copy(db, QStringLiteral("INSERT INTO folio SELECT uuid, ?, %1, props_at, props_attrs " + Statement copy(db, QStringLiteral("INSERT INTO folio SELECT uuid, ?, %1, 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 : { @@ -942,7 +945,8 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB //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 uuid FROM element WHERE folio_uuid = ? AND uuid = ? AND info_at IS NOT NULL")); + //Its part has the place they go back to (QetContainer::split()) + Statement find(db, QStringLiteral("SELECT uuid FROM element WHERE folio_uuid = ? AND uuid = ?")); 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)); @@ -979,8 +983,7 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB row += take(d, FolioData); QDomElement props = d.firstChildElement(QStringLiteral("properties")); const bool move_props = !props.isNull() && listBlock(props, QStringLiteral("property")); - row << (move_props ? QVariant(childIndex(props)) : QVariant()) - << (move_props ? blockJson(props) : QVariant()); + row << (move_props ? blockJson(props) : QVariant()); ok = ok && folio.run(row); if (move_props) { moveProperties(props, folio_property, {id}); @@ -998,24 +1001,20 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB QDomElement links = e.firstChildElement(QStringLiteral("links_uuids")); if (!info.isNull() && !listBlock(info, QStringLiteral("elementInformation"))) info = QDomElement(); if (!links.isNull() && !listBlock(links, QStringLiteral("link_uuid"))) links = QDomElement(); - const int info_at = info.isNull() ? -1 : childIndex(info); - const int links_at = links.isNull() ? -1 : childIndex(links); - erow << (info_at < 0 ? QVariant() : QVariant(info_at)) - << (info_at < 0 ? QVariant() : blockJson(info)) - << (links_at < 0 ? QVariant() : QVariant(links_at)) - << (links_at < 0 ? QVariant() : blockJson(links)); + erow << (info.isNull() ? QVariant() : blockJson(info)) + << (links.isNull() ? QVariant() : blockJson(links)); ok = ok && element.run(erow); - if (info_at >= 0) + if (!info.isNull()) moveInformation(info, key); - if (links_at >= 0) { + if (!links.isNull()) { 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")}, QStringLiteral("link_uuid"))}); } - if (info_at >= 0) leaveMarker(info); - if (links_at >= 0) leaveMarker(links); + if (!info.isNull()) leaveMarker(info); + if (!links.isNull()) leaveMarker(links); if (!own_key) e.setAttribute(KeyAttribute, key); } } @@ -1088,7 +1087,7 @@ bool QetContainerDb::restore(QDomElement project, const QByteArray &database, QS Statement s(db, QStringLiteral("SELECT key, value FROM meta")); while (s.next()) meta.insert(s.value(0).toString(), s.value(1)); } - if (meta.contains(QStringLiteral("props_at"))) { + if (hasMarker(project, QStringLiteral("properties"))) { QDomElement props = document.createElement(QStringLiteral("properties")); setAttributesJson(props, meta.value(QStringLiteral("props_attrs"))); Statement s(db, QStringLiteral("SELECT name, value, attrs FROM project_property ORDER BY ord")); @@ -1142,8 +1141,8 @@ bool QetContainerDb::currentFormat(const QByteArray &database) return false; //Its rows are copied as they are: the same columns as this writes const QList> columns = { - {QStringLiteral("folio"), int(FolioData.size()) + 4}, - {QStringLiteral("element"), int(ElementData.size()) + 6}, + {QStringLiteral("folio"), int(FolioData.size()) + 3}, + {QStringLiteral("element"), int(ElementData.size()) + 4}, {QStringLiteral("conductor"), int(ConductorData.size()) + 2}}; for (const auto &table : columns) { Statement s(db, QStringLiteral("SELECT count(*) FROM pragma_table_info('%1')").arg(table.first)); diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index ab25c619a..c3a02bbda 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -1984,6 +1984,16 @@ bool QETDiagramEditor::openAndAddProject( addProject(project); DialogWaiting::dropInstance(); + if (interactive && project->isFromLaterVersion()) { + QET::QetMessageBox::warning( + this, + tr("Opening the file read-only", "message box title"), + tr("This project was saved by a later version of QElectroTech. " + "It is opened read-only, so that saving it cannot lose what " + "this version does not know. Use Save As to keep a copy " + "in this version's format.")); + } + //Report font descriptions which could not be read as-is (written by //an incompatible Qt version or corrupted), so the user learns about //it from somewhere else than the console. See issue #553. diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index afd94ab65..7a43faf9c 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -484,11 +484,12 @@ QETProject::ProjectState QETProject::openFile(QFile *file) m_container_files.clear(); m_folio_parts.clear(); bool read = false; + int container_format = 0; if (QetContainer::isZip(content)) { QList entries; read = QetZip::fromBytes(content, &entries, &read_error) && QetContainer::join(entries, &xml_project, &read_error, &m_folio_parts, - &m_container_pictures); + &m_container_pictures, &container_format); if (read) for (const QetZip::Entry &entry : std::as_const(entries)) m_container_files.insert(entry.name, entry.data); @@ -530,9 +531,16 @@ QETProject::ProjectState QETProject::openFile(QFile *file) << xml_parse_ms / 1000.0 << ", content " << (total_ms - xml_parse_ms) / 1000.0 << ")"; - if (!fi.isWritable()) { + //Written by a later QElectroTech that lets this one read it: saving + //here would drop what this version does not know + m_from_later_version = container_format > QetContainer::Format; + if (!fi.isWritable() || m_from_later_version) { setReadOnly(true); } + if (m_from_later_version) { + qInfo().noquote() << QStringLiteral("Project %1 is of .qetz format %2 (this version writes %3): opened read-only") + .arg(fi.fileName()).arg(container_format).arg(QetContainer::Format); + } if(opened_here) { file->close(); } diff --git a/sources/qetproject.h b/sources/qetproject.h index b98e9cf0a..621097e55 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -334,6 +334,8 @@ class QETProject : public QObject bool close(); QETResult write(); bool isReadOnly() const; + /// Opened from a .qetz of a later format (opened read-only) + bool isFromLaterVersion() const {return m_from_later_version;} void setReadOnly(bool); bool isEmpty() const; ElementsLocation importElement(ElementsLocation &location); @@ -490,6 +492,7 @@ class QETProject : public QObject bool m_modified = false; /// Whether the project is read only bool m_read_only = false; + bool m_from_later_version = false; /// Filepath for which this project is considered read only QString read_only_file_path_; /// Default dimensions and properties for new diagrams created within the project diff --git a/tests/qttest/tst_qetcontainer.cpp b/tests/qttest/tst_qetcontainer.cpp index 9e1f0bb77..ef5914eb2 100644 --- a/tests/qttest/tst_qetcontainer.cpp +++ b/tests/qttest/tst_qetcontainer.cpp @@ -379,6 +379,43 @@ private slots: } // A file whose manifest needs a later reader is refused, not half read + // A later format whose min-reader allows this one opens, and join() says + // it is later: QElectroTech opens it read-only + void laterFormatAllowedRead() + { + QDomDocument original; + QList entries = splitFile(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"), &original); + QVERIFY(!entries.isEmpty()); + const int later = QetContainer::Format + 1; + for (QetZip::Entry &e : entries) + if (e.name == QLatin1String("manifest.xml")) + e.data = QString::fromUtf8(e.data).replace(QRegularExpression(QStringLiteral(" format=\"\\d+\"")), + QStringLiteral(" format=\"%1\"").arg(later)).toUtf8(); + alterDatabase(entries, QStringLiteral("UPDATE meta SET value = '%1' WHERE key = 'format'") + .arg(later).toUtf8().constData()); + QDomDocument back; + QString error; + int format = 0; + QVERIFY2(QetContainer::join(entries, &back, &error, nullptr, nullptr, &format), qPrintable(error)); + QCOMPARE(format, later); + const QString d = difference(withoutComputed(original).documentElement(), back.documentElement(), QString()); + QVERIFY2(d.isEmpty(), qPrintable(d)); + } + + // The database holds data, not where a block sat in the XML: the folio + // file's marker says where it goes back + void noPositionColumns() + { + QDomDocument original; + const QList entries = splitFile(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"), &original); + const QByteArray database = entry(entries, QStringLiteral("project.sqlite")); + QCOMPARE(query(database, "SELECT group_concat(name) FROM pragma_table_info('element')"), + QStringLiteral("uuid,folio_uuid,prefix,freezeLabel,info_attrs,links_attrs")); + QCOMPARE(query(database, "SELECT count(*) FROM pragma_table_info('folio') WHERE name LIKE '%_at'"), + QStringLiteral("0")); + QCOMPARE(query(database, "SELECT count(*) FROM meta WHERE key = 'props_at'"), QStringLiteral("0")); + } + void laterFormatRefused() { QDomDocument original; @@ -455,8 +492,8 @@ private slots: QTest::addColumn("sql"); QTest::addColumn("said"); QTest::newRow("symbol row of no symbol") - << QByteArray("INSERT INTO element SELECT 'x', folio_uuid, prefix, freezeLabel, info_at, info_attrs," - " links_at, links_attrs FROM element LIMIT 1") + << QByteArray("INSERT INTO element SELECT 'x', folio_uuid, prefix, freezeLabel, info_attrs," + " links_attrs FROM element LIMIT 1") << QStringLiteral("symbol rows"); QTest::newRow("symbol row twice") << QByteArray("INSERT INTO element SELECT * FROM element LIMIT 1")