diff --git a/src/database_sqlite.c b/src/database_sqlite.c index 446b14cb..e479e618 100644 --- a/src/database_sqlite.c +++ b/src/database_sqlite.c @@ -61,9 +61,10 @@ static prof_msg_type_t _get_message_type_type(const char* const type); static prof_enc_t _get_message_enc_type(const char* const encstr); static int _get_db_version(void); static gboolean _migrate_to_v2(void); +static gboolean _migrate_to_v3(void); static gboolean _check_available_space_for_db_migration(char* path_to_db); -static const int latest_version = 2; +static const int latest_version = 3; gboolean db_sqlite_is_open(void) @@ -187,7 +188,7 @@ _sqlite_init(ProfAccount* account) "`timestamp` TEXT, " "`type` TEXT, " "`stanza_id` TEXT, " - "`archive_id` TEXT, " + "`archive_id` TEXT UNIQUE, " "`encryption` TEXT, " "`marked_read` INTEGER, " "`replace_id` TEXT, " @@ -228,8 +229,10 @@ _sqlite_init(ProfAccount* account) } if (db_version == -1) { - query = "INSERT OR IGNORE INTO `DbVersion` (`version`) VALUES ('2')"; - if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + auto_sqlite char* init_version_query = sqlite3_mprintf( + "INSERT INTO `DbVersion` (`version`) VALUES (%d) ON CONFLICT(`version`) DO NOTHING", + latest_version); + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, init_version_query, NULL, 0, &err_msg)) { goto out; } db_version = _get_db_version(); @@ -246,6 +249,10 @@ _sqlite_init(ProfAccount* account) cons_show_error("Database Initialization Error: Unable to migrate database to version 2. Please, check error logs for details."); goto out; } + if (db_version < 3 && (!_check_available_space_for_db_migration(filename) || !_migrate_to_v3())) { + cons_show_error("Database Initialization Error: Unable to migrate database to version 3. Please, check error logs for details."); + goto out; + } cons_show("Database schema migration was successful."); } @@ -799,7 +806,8 @@ _migrate_to_v2(void) "replace_id = COALESCE(NULLIF(replace_id, ''), NULL), " "type = COALESCE(NULLIF(type, ''), NULL), " "encryption = COALESCE(NULLIF(encryption, ''), NULL);", - "UPDATE `DbVersion` SET `version` = 2;", + "DELETE FROM `DbVersion`;", + "INSERT INTO `DbVersion` (`version`) VALUES (2);", "END TRANSACTION" }; @@ -829,6 +837,82 @@ cleanup: return FALSE; } +static gboolean +_migrate_to_v3(void) +{ + char* err_msg = NULL; + + const char* sql_statements[] = { + "BEGIN TRANSACTION", + "CREATE TABLE `ChatLogs_v3_migration` (" + "`id` INTEGER PRIMARY KEY AUTOINCREMENT, " + "`from_jid` TEXT NOT NULL, " + "`to_jid` TEXT NOT NULL, " + "`from_resource` TEXT, " + "`to_resource` TEXT, " + "`message` TEXT, " + "`timestamp` TEXT, " + "`type` TEXT, " + "`stanza_id` TEXT, " + "`archive_id` TEXT UNIQUE, " + "`encryption` TEXT, " + "`marked_read` INTEGER, " + "`replace_id` TEXT, " + "`replaces_db_id` INTEGER, " + "`replaced_by_db_id` INTEGER)", + + "INSERT INTO `ChatLogs_v3_migration` " + "SELECT * FROM `ChatLogs` " + "WHERE `archive_id` IS NULL " + "UNION ALL " + "SELECT * FROM (SELECT * FROM `ChatLogs` WHERE `archive_id` IS NOT NULL GROUP BY `archive_id`)", + + "DROP TABLE `ChatLogs` ", + "ALTER TABLE `ChatLogs_v3_migration` RENAME TO `ChatLogs` ", + + "CREATE INDEX ChatLogs_timestamp_IDX ON `ChatLogs` (`timestamp`)", + "CREATE INDEX ChatLogs_to_from_jid_IDX ON `ChatLogs` (`to_jid`, `from_jid`)", + "CREATE TRIGGER update_corrected_message " + "AFTER INSERT ON ChatLogs " + "FOR EACH ROW " + "WHEN NEW.replaces_db_id IS NOT NULL " + "BEGIN " + "UPDATE ChatLogs " + "SET replaced_by_db_id = NEW.id " + "WHERE id = NEW.replaces_db_id; " + "END;", + + "DELETE FROM `DbVersion`;", + "INSERT INTO `DbVersion` (`version`) VALUES (3);", + "END TRANSACTION" + }; + + int statements_count = sizeof(sql_statements) / sizeof(sql_statements[0]); + + for (int i = 0; i < statements_count; i++) { + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, sql_statements[i], NULL, 0, &err_msg)) { + log_error("SQLite error in _migrate_to_v3() on statement %d: %s", i, err_msg); + if (err_msg) { + sqlite3_free(err_msg); + err_msg = NULL; + } + goto cleanup; + } + } + + return TRUE; + +cleanup: + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, "ROLLBACK;", NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to ROLLBACK: %s", err_msg); + if (err_msg) { + sqlite3_free(err_msg); + } + } + + return FALSE; +} + static gboolean _check_available_space_for_db_migration(char* path_to_db) { diff --git a/src/event/server_events.c b/src/event/server_events.c index 83fea6f3..09b8af0a 100644 --- a/src/event/server_events.c +++ b/src/event/server_events.c @@ -81,7 +81,9 @@ sv_ev_login_account_success(char* account_name, gboolean secured) omemo_on_connect(account); #endif - log_database_init(account); + if (!log_database_init(account)) { + log_error("Failed to initialize database for account: %s", account->jid); + } vcard_user_refresh(); avatar_pep_subscribe();