[network/ruqola] src/core: MAke sure to delete all connection

Laurent Montel <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit c1ced96a786b7d03d9abd0ce31ac4469228d0099 by Laurent Montel.
Committed on 16/08/2026 at 13:11.
Pushed by mlaurent into branch 'master'.

MAke sure to delete all connection

M  +5    -0    src/core/accountmanager.cpp
M  +48   -0    src/core/localdatabase/autotests/localdatabasebasetest.cpp
M  +1    -0    src/core/localdatabase/autotests/localdatabasebasetest.h
M  +57   -4    src/core/localdatabase/localdatabasebase.cpp
M  +5    -0    src/core/localdatabase/localdatabasebase.h

https://invent.kde.org/network/ruqola/-/commit/c1ced96a786b7d03d9abd0ce31ac4469228d0099

diff --git a/src/core/accountmanager.cpp b/src/core/accountmanager.cpp
index 08e98894a2..7d666440af 100644
--- a/src/core/accountmanager.cpp
+++ b/src/core/accountmanager.cpp
@@ -6,6 +6,7 @@
 
 #include "accountmanager.h"
 #include "job/validateinviteserverjob.h"
+#include "localdatabase/localdatabasebase.h"
 #include "localdatabase/localdatabaseutils.h"
 #include "managerdatapaths.h"
 #include "model/rocketchataccountfilterproxymodel.h"
@@ -474,6 +475,10 @@ void AccountManager::removeLogs(const QString &accountName)
 
 void AccountManager::removeDatabaseAccount(const QString &accountName)
 {
+    // Sqlite connections are process-wide and outlive the account: close them before removing the
+    // files, otherwise re-adding an account with the same name would reuse a connection pointing at
+    // a deleted file (writes lost, tables gone).
+    LocalDatabaseBase::removeDataBaseConnections(accountName);
     {
         const QString directory = LocalDatabaseUtils::localAccountsDatabasePath() + accountName;
         removeDirectory(directory);
diff --git a/src/core/localdatabase/autotests/localdatabasebasetest.cpp b/src/core/localdatabase/autotests/localdatabasebasetest.cpp
index 7e98be867e..53f11fc8cf 100644
--- a/src/core/localdatabase/autotests/localdatabasebasetest.cpp
+++ b/src/core/localdatabase/autotests/localdatabasebasetest.cpp
@@ -7,6 +7,8 @@
 #include "localdatabasebasetest.h"
 
 #include "localdatabase/localdatabasebase.h"
+#include <QSqlDatabase>
+#include <QTemporaryDir>
 #include <QTest>
 
 using namespace Qt::Literals::StringLiterals;
@@ -22,6 +24,20 @@ public:
     {
         return databaseName(name);
     }
+    [[nodiscard]] QString schemaDataBase() const override
+    {
+        return u"CREATE TABLE TESTTABLE (identifier TEXT PRIMARY KEY NOT NULL)"_s;
+    }
+    [[nodiscard]] bool createDataBase(const QString &accountName)
+    {
+        QSqlDatabase db;
+        return initializeDataBase(accountName, db);
+    }
+    [[nodiscard]] bool createDataBase(const QString &accountName, const QByteArray &roomId)
+    {
+        QSqlDatabase db;
+        return initializeDataBase(accountName, roomId, db);
+    }
 };
 
 QTEST_MAIN(LocalDatabaseBaseTest)
@@ -69,4 +85,36 @@ void LocalDatabaseBaseTest::shouldHaveDefaultValues()
         QCOMPARE(w.currentDatabaseName(u"kde"_s), u"e2e-rooms-kde"_s);
     }
 }
+
+void LocalDatabaseBaseTest::shouldRemoveDataBaseConnections()
+{
+    QTemporaryDir tmpDir;
+    QVERIFY(tmpDir.isValid());
+    const QString basePath = tmpDir.path() + u'/';
+
+    TestLocalDatabaseBase rooms(basePath + u"rooms/"_s, LocalDatabaseBase::DatabaseType::Rooms);
+    TestLocalDatabaseBase messages(basePath + u"messages/"_s, LocalDatabaseBase::DatabaseType::Messages);
+    QVERIFY(rooms.createDataBase(u"kde"_s));
+    QVERIFY(rooms.createDataBase(u"kde2"_s));
+    QVERIFY(messages.createDataBase(u"kde"_s, "roomId1"));
+
+    QVERIFY(QSqlDatabase::contains(u"rooms-kde"_s));
+    QVERIFY(QSqlDatabase::contains(u"rooms-kde2"_s));
+    QVERIFY(QSqlDatabase::contains(u"messages-kde-roomId1"_s));
+
+    LocalDatabaseBase::removeDataBaseConnections(u"kde"_s);
+
+    QVERIFY(!QSqlDatabase::contains(u"rooms-kde"_s));
+    QVERIFY(!QSqlDatabase::contains(u"messages-kde-roomId1"_s));
+    // An account whose name merely starts with the removed one must be left alone
+    QVERIFY(QSqlDatabase::contains(u"rooms-kde2"_s));
+
+    // The connection is gone but the file is still there: reopening must find the existing schema
+    QVERIFY(rooms.createDataBase(u"kde"_s));
+    QVERIFY(QSqlDatabase::contains(u"rooms-kde"_s));
+
+    LocalDatabaseBase::removeDataBaseConnections(u"kde"_s);
+    LocalDatabaseBase::removeDataBaseConnections(u"kde2"_s);
+}
+
 #include "moc_localdatabasebasetest.cpp"
diff --git a/src/core/localdatabase/autotests/localdatabasebasetest.h b/src/core/localdatabase/autotests/localdatabasebasetest.h
index 7065fb82c1..8d60923e23 100644
--- a/src/core/localdatabase/autotests/localdatabasebasetest.h
+++ b/src/core/localdatabase/autotests/localdatabasebasetest.h
@@ -16,4 +16,5 @@ public:
     ~LocalDatabaseBaseTest() override = default;
 private Q_SLOTS:
     void shouldHaveDefaultValues();
+    void shouldRemoveDataBaseConnections();
 };
diff --git a/src/core/localdatabase/localdatabasebase.cpp b/src/core/localdatabase/localdatabasebase.cpp
index bfeb975105..7d49aff197 100644
--- a/src/core/localdatabase/localdatabasebase.cpp
+++ b/src/core/localdatabase/localdatabasebase.cpp
@@ -52,12 +52,11 @@ QString LocalDatabaseBase::schemaDataBase() const
     return {};
 }
 
-QString LocalDatabaseBase::databaseName(const QString &name) const
+QString LocalDatabaseBase::databaseNamePrefix(DatabaseType type)
 {
     QString prefix;
-    switch (mDatabaseType) {
+    switch (type) {
     case DatabaseType::Unknown:
-        qCWarning(RUQOLA_DATABASE_LOG) << "Unknown data base it's a bug" << name;
         break;
     case DatabaseType::Accounts:
         prefix = u"accounts-"_s;
@@ -86,7 +85,61 @@ QString LocalDatabaseBase::databaseName(const QString &name) const
     case DatabaseType::Logger:
         break;
     }
-    return prefix + name;
+    return prefix;
+}
+
+QString LocalDatabaseBase::databaseName(const QString &name) const
+{
+    if (mDatabaseType == DatabaseType::Unknown) {
+        qCWarning(RUQOLA_DATABASE_LOG) << "Unknown data base it's a bug" << name;
+    }
+    return databaseNamePrefix(mDatabaseType) + name;
+}
+
+void LocalDatabaseBase::removeDataBaseConnections(const QString &accountName)
+{
+    if (accountName.isEmpty()) {
+        return;
+    }
+    // All types: the caller removes the account as a whole, and each database class registers its
+    // connections under its own prefix.
+    static constexpr DatabaseType allTypes[] = {
+        DatabaseType::Accounts,
+        DatabaseType::Rooms,
+        DatabaseType::Messages,
+        DatabaseType::Logger,
+        DatabaseType::Global,
+        DatabaseType::E2E,
+        DatabaseType::E2ERooms,
+        DatabaseType::PendingTypedInfo,
+        DatabaseType::RoomSubscriptions,
+    };
+
+    const QStringList connectionNames = QSqlDatabase::connectionNames();
+    QStringList namesToRemove;
+    for (const DatabaseType type : allTypes) {
+        const QString accountConnection = databaseNamePrefix(type) + accountName;
+        // Per-room databases are registered as "<prefix><accountName>-<roomId>"
+        const QString roomConnectionPrefix = accountConnection + u'-';
+        for (const QString &name : connectionNames) {
+            if (name == accountConnection || name.startsWith(roomConnectionPrefix)) {
+                namesToRemove.append(name);
+            }
+        }
+    }
+    namesToRemove.removeDuplicates();
+
+    for (const QString &name : std::as_const(namesToRemove)) {
+        {
+            // Don't reopen it just to close it, and let the copy die before removeDatabase()
+            QSqlDatabase db = QSqlDatabase::database(name, false);
+            if (db.isOpen()) {
+                db.close();
+            }
+        }
+        QSqlDatabase::removeDatabase(name);
+    }
+    qCDebug(RUQOLA_DATABASE_LOG) << "Removed" << namesToRemove.count() << "database connection(s) for account" << accountName;
 }
 
 void LocalDatabaseBase::setDatabaseLogger(RocketChatRestApi::AbstractLogger *logger)
diff --git a/src/core/localdatabase/localdatabasebase.h b/src/core/localdatabase/localdatabasebase.h
index f9c3e7567b..d6c19ce1f7 100644
--- a/src/core/localdatabase/localdatabasebase.h
+++ b/src/core/localdatabase/localdatabasebase.h
@@ -35,6 +35,10 @@ public:
     [[nodiscard]] QString schemaDatabaseStr() const;
     void setDatabaseLogger(RocketChatRestApi::AbstractLogger *logger);
 
+    // Closes and unregisters every sqlite connection belonging to this account, whatever the
+    // database type. Must be called before the account files are deleted from disk.
+    static void removeDataBaseConnections(const QString &accountName);
+
 protected:
     [[nodiscard]] virtual QString schemaDataBase() const;
     [[nodiscard]] bool initializeDataBase(const QString &accountName, const QByteArray &roomId, QSqlDatabase &db);
@@ -42,6 +46,7 @@ protected:
     [[nodiscard]] bool checkDataBase(const QString &accountName, const QByteArray &roomId, QSqlDatabase &db);
     [[nodiscard]] bool checkDataBase(const QString &accountName, QSqlDatabase &db);
     [[nodiscard]] QString databaseName(const QString &name) const;
+    [[nodiscard]] static QString databaseNamePrefix(DatabaseType type);
 
     const QString mBasePath;
     const DatabaseType mDatabaseType = DatabaseType::Unknown;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.