Fix element_info orphan row causing UNIQUE constraint errors on undo

Reviving #664, closed 2026-09-18 purely to clear a maintainer review
backlog (#630), not on merit; not superseded. Rewritten fresh against
current master rather than merged from the old branch -- that branch
predates the Qt6-only switch and much of dataBase/projectdatabase.cpp's
later rewrite, and the two had diverged too far for a textual merge to
be trustworthy.

projectDataBase::removeElement() only ran DELETE FROM element WHERE
uuid=:uuid. It never touched element_info, even though every element
also has a row there (element_uuid is its PRIMARY KEY, with a FOREIGN
KEY back to element.uuid that isn't enforced by this connection -- no
ON DELETE CASCADE in effect). So deleting an element left its
element_info row orphaned.

Re-adding an element with that same uuid later -- undo of that same
deletion, or a redo replaying it -- goes through addElement(), which
INSERTs into both tables. The element insert succeeds (that row really
was removed). The element_info insert hits the orphaned row's primary
key and fails, silently: the error is logged and swallowed, so the
element re-enters the scene with no element_info row at all, and
nothing later re-syncs it.

removeDiagram() already cascades this cleanup when a whole folio is
removed (a later, unrelated addition) -- confirmed on current master --
but that path never runs for a single element removed on its own,
which is the case this fixes.

Verified on the built binary, not just read: placed an element, deleted
it (Ctrl+A, Delete), undid the deletion (Ctrl+Z). Reverting just this
fix and repeating the identical sequence reproduces the exact reported
error:

  Debug: projectDataBase::addElement insert element info error :
  QSqlError("1555", "Unable to fetch row",
  "UNIQUE constraint failed: element_info.element_uuid")

With the fix, the same sequence produces nothing. Qt 6.10.2, ctest
13/13.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-23 16:13:04 +12:00
parent 6d8459d647
commit 3eea3059bf
2 changed files with 31 additions and 2 deletions
+30 -2
View File
@@ -319,10 +319,23 @@ void projectDataBase::addElement(Element *element)
void projectDataBase::removeElement(Element *element)
{
m_content_changed = true;
bool changed = false;
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();
if (m_remove_element_query.exec()) {
changed = true;
} else {
qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError();
}
m_remove_element_info_query.bindValue(":uuid", element->uuid().toString());
if (m_remove_element_info_query.exec()) {
changed = true;
} else {
qDebug() << "projectDataBase::removeElement remove element_info error : " << m_remove_element_info_query.lastError();
}
if (changed) {
emit dataBaseUpdated();
}
}
@@ -1159,6 +1172,21 @@ void projectDataBase::prepareQuery()
m_remove_element_query = QSqlQuery(m_data_base);
m_remove_element_query.prepare(remove_element);
//REMOVE ELEMENT INFO
//element_info has no ON DELETE CASCADE (foreign keys aren't
//enforced by this connection), so removeElement() must clear it
//explicitly. Without this, the row is orphaned under the removed
//element's uuid, and re-adding an element with that same uuid
//later -- undo of this same removal, or a redo replaying it --
//hits element_info's PRIMARY KEY constraint on element_uuid: the
//element re-add succeeds, but its element_info insert silently
//fails and is lost. removeDiagram()'s cascade already clears this
//table when a whole folio goes, but that does not run for a
//single element removed on its own.
QString remove_element_info("DELETE FROM element_info WHERE element_uuid=:uuid");
m_remove_element_info_query = QSqlQuery(m_data_base);
m_remove_element_info_query.prepare(remove_element_info);
//UPDATE ELEMENT INFO
QString update_str("UPDATE element_info SET ");
for (auto string : QETInformation::elementInfoKeys()) {