Make sure DB upgrades happen atomically

This commit is contained in:
Luis Ángel San Martín
2025-06-15 18:12:07 +02:00
parent c621c2705b
commit 6a4f9730f5

View File

@ -901,224 +901,229 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &libraryPath)
{
QSqlDatabase db = loadDatabaseFromFile(libraryDatabasePath);
if (db.isValid() && db.isOpen()) {
if (pre7) // TODO: execute only if previous version was < 7.0
{
// new 7.0 fields
QStringList columnDefs;
columnDefs << "hasBeenOpened BOOLEAN DEFAULT 0"
<< "rating INTEGER DEFAULT 0"
<< "currentPage INTEGER DEFAULT 1"
<< "bookmark1 INTEGER DEFAULT -1"
<< "bookmark2 INTEGER DEFAULT -1"
<< "bookmark3 INTEGER DEFAULT -1"
<< "brightness INTEGER DEFAULT -1"
<< "contrast INTEGER DEFAULT -1"
<< "gamma INTEGER DEFAULT -1";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
// TODO update hasBeenOpened value
if (pre7_1) {
{
if (!db.transaction()) {
QLOG_ERROR() << "Failed to start transaction for database update";
returnValue = false;
} else {
if (pre7) {
// new 7.0 fields
QStringList columnDefs;
columnDefs << "finished BOOLEAN DEFAULT 0"
<< "completed BOOLEAN DEFAULT 1";
bool successAddingColumns = addColumns("folder", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
columnDefs << "hasBeenOpened BOOLEAN DEFAULT 0"
<< "rating INTEGER DEFAULT 0"
<< "currentPage INTEGER DEFAULT 1"
<< "bookmark1 INTEGER DEFAULT -1"
<< "bookmark2 INTEGER DEFAULT -1"
<< "bookmark3 INTEGER DEFAULT -1"
<< "brightness INTEGER DEFAULT -1"
<< "contrast INTEGER DEFAULT -1"
<< "gamma INTEGER DEFAULT -1";
{ // comic_info
QStringList columnDefs;
columnDefs << "comicVineID TEXT DEFAULT NULL";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
}
if (pre8) {
bool successCreatingNewTables = createV8Tables(db);
returnValue = returnValue && successCreatingNewTables;
}
if (pre9_5) {
{ // folder
QStringList columnDefs;
// a full library update is needed after updating the table
columnDefs << "numChildren INTEGER";
columnDefs << "firstChildHash TEXT";
columnDefs << "customImage TEXT";
bool successAddingColumns = addColumns("folder", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
{ // comic_info
QStringList columnDefs;
columnDefs << "lastTimeOpened INTEGER";
columnDefs << "coverSizeRatio REAL";
columnDefs << "originalCoverSize TEXT";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
QSqlQuery queryIndexLastTimeOpened(db);
bool successCreatingIndex = queryIndexLastTimeOpened.exec("CREATE INDEX last_time_opened_index ON comic_info (lastTimeOpened)");
returnValue = returnValue && successCreatingIndex;
}
// update folders info
{
DBHelper::updateChildrenInfo(db);
}
{
QSqlQuery selectQuery(db);
selectQuery.prepare("SELECT id, hash FROM comic_info");
selectQuery.exec();
db.transaction();
QSqlQuery updateCoverInfo(db);
updateCoverInfo.prepare("UPDATE comic_info SET coverSizeRatio = :coverSizeRatio WHERE id = :id");
QImageReader thumbnail;
while (selectQuery.next()) {
auto coverPath = LibraryPaths::coverPath(libraryPath, selectQuery.value(1).toString());
thumbnail.setFileName(coverPath);
float coverSizeRatio = static_cast<float>(thumbnail.size().width()) / thumbnail.size().height();
updateCoverInfo.bindValue(":coverSizeRatio", coverSizeRatio);
updateCoverInfo.bindValue(":id", selectQuery.value(0));
updateCoverInfo.exec();
if (pre7_1) {
{
QStringList columnDefs;
columnDefs << "finished BOOLEAN DEFAULT 0"
<< "completed BOOLEAN DEFAULT 1";
bool successAddingColumns = addColumns("folder", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
db.commit();
{ // comic_info
QStringList columnDefs;
columnDefs << "comicVineID TEXT DEFAULT NULL";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
}
}
if (pre9_8) {
{ // comic_info
QStringList columnDefs;
columnDefs << "manga BOOLEAN DEFAULT 0";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
if (pre8) {
bool successCreatingNewTables = createV8Tables(db);
returnValue = returnValue && successCreatingNewTables;
}
{ // folder
QStringList columnDefs;
columnDefs << "manga BOOLEAN DEFAULT 0";
bool successAddingColumns = addColumns("folder", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
}
if (pre9_13) {
{ // comic_info
QStringList columnDefs;
columnDefs << "added INTEGER";
columnDefs << "type INTEGER DEFAULT 0"; // 0 = comic, 1 = manga, 2 = manga left to right, 3 = webcomic,
columnDefs << "editor TEXT";
columnDefs << "imprint TEXT";
columnDefs << "teams TEXT";
columnDefs << "locations TEXT";
columnDefs << "series TEXT";
columnDefs << "alternateSeries TEXT";
columnDefs << "alternateNumber TEXT";
columnDefs << "alternateCount INTEGER";
columnDefs << "languageISO TEXT";
columnDefs << "seriesGroup TEXT";
columnDefs << "mainCharacterOrTeam TEXT";
columnDefs << "review TEXT";
columnDefs << "tags TEXT";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
if (pre9_5) {
{ // folder
QStringList columnDefs;
// a full library update is needed after updating the table
columnDefs << "numChildren INTEGER";
columnDefs << "firstChildHash TEXT";
columnDefs << "customImage TEXT";
bool successAddingColumns = addColumns("folder", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
QSqlQuery updateTypeQueryToManga(db);
updateTypeQueryToManga.prepare("UPDATE comic_info SET type = manga");
bool successMigratingManga = updateTypeQueryToManga.exec();
returnValue = returnValue && successMigratingManga;
{ // comic_info
QStringList columnDefs;
columnDefs << "lastTimeOpened INTEGER";
columnDefs << "coverSizeRatio REAL";
columnDefs << "originalCoverSize TEXT";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
QSqlQuery updateNumberQueryToBis(db);
updateNumberQueryToBis.prepare("UPDATE comic_info SET number = number + 0.5 WHERE isBis = 1");
bool successMigratingBis = updateNumberQueryToBis.exec();
returnValue = returnValue && successMigratingBis;
}
{ // folder
QStringList columnDefs;
columnDefs << "added INTEGER";
columnDefs << "updated INTEGER";
columnDefs << "type INTEGER DEFAULT 0";
QSqlQuery queryIndexLastTimeOpened(db);
bool successCreatingIndex = queryIndexLastTimeOpened.exec("CREATE INDEX last_time_opened_index ON comic_info (lastTimeOpened)");
returnValue = returnValue && successCreatingIndex;
}
bool successAddingColumns = addColumns("folder", columnDefs, db);
returnValue = returnValue && successAddingColumns;
// update folders info
{
DBHelper::updateChildrenInfo(db);
}
QSqlQuery updateTypeQueryToManga(db);
updateTypeQueryToManga.prepare("UPDATE folder SET type = manga");
bool successMigratingManga = updateTypeQueryToManga.exec();
returnValue = returnValue && successMigratingManga;
}
}
{
QSqlQuery selectQuery(db);
selectQuery.prepare("SELECT id, hash FROM comic_info");
selectQuery.exec();
// ensure that INTEGER types migrated to TEXT are actually changed in the table definition to avoid internal type castings, this happened in 9.13 but a migration wasn't shipped with that version.
if (pre9_14) {
{
bool pre9_14_successfulMigration = true;
db.transaction();
QSqlQuery pragmaFKOFF(db);
pragmaFKOFF.prepare("PRAGMA foreign_keys=OFF");
pre9_14_successfulMigration = pre9_14_successfulMigration && pragmaFKOFF.exec();
QSqlQuery updateCoverInfo(db);
updateCoverInfo.prepare("UPDATE comic_info SET coverSizeRatio = :coverSizeRatio WHERE id = :id");
db.transaction();
QImageReader thumbnail;
while (selectQuery.next()) {
auto coverPath = LibraryPaths::coverPath(libraryPath, selectQuery.value(1).toString());
thumbnail.setFileName(coverPath);
pre9_14_successfulMigration = pre9_14_successfulMigration && createComicInfoTable(db, "comic_info_migration");
float coverSizeRatio = static_cast<float>(thumbnail.size().width()) / thumbnail.size().height();
updateCoverInfo.bindValue(":coverSizeRatio", coverSizeRatio);
updateCoverInfo.bindValue(":id", selectQuery.value(0));
QSqlQuery copyComicInfoToComicInfoMigration(db);
copyComicInfoToComicInfoMigration.prepare("INSERT INTO comic_info_migration SELECT * FROM comic_info");
pre9_14_successfulMigration = pre9_14_successfulMigration && copyComicInfoToComicInfoMigration.exec();
updateCoverInfo.exec();
}
QSqlQuery dropComicInfo(db);
dropComicInfo.prepare("DROP TABLE comic_info");
pre9_14_successfulMigration = pre9_14_successfulMigration && dropComicInfo.exec();
QSqlQuery renameComicInfoMigrationToComicInfo(db);
renameComicInfoMigrationToComicInfo.prepare("ALTER TABLE comic_info_migration RENAME TO comic_info");
pre9_14_successfulMigration = pre9_14_successfulMigration && renameComicInfoMigrationToComicInfo.exec();
if (pre9_14_successfulMigration)
db.commit();
else
db.rollback();
QSqlQuery pragmaFKON1("PRAGMA foreign_keys=ON", db);
returnValue = returnValue && pre9_14_successfulMigration;
}
}
}
if (pre9_16) {
{ // comic_info
QStringList columnDefs;
columnDefs << "imageFiltersJson TEXT";
columnDefs << "lastTimeImageFiltersSet INTEGER DEFAULT 0";
if (pre9_8) {
{ // comic_info
QStringList columnDefs;
columnDefs << "manga BOOLEAN DEFAULT 0";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
{ // folder
QStringList columnDefs;
columnDefs << "manga BOOLEAN DEFAULT 0";
bool successAddingColumns = addColumns("folder", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
}
columnDefs << "lastTimeCoverSet INTEGER DEFAULT 0";
columnDefs << "usesExternalCover BOOLEAN DEFAULT 0";
if (pre9_13) {
{ // comic_info
QStringList columnDefs;
columnDefs << "added INTEGER";
columnDefs << "type INTEGER DEFAULT 0"; // 0 = comic, 1 = manga, 2 = manga left to right, 3 = webcomic,
columnDefs << "editor TEXT";
columnDefs << "imprint TEXT";
columnDefs << "teams TEXT";
columnDefs << "locations TEXT";
columnDefs << "series TEXT";
columnDefs << "alternateSeries TEXT";
columnDefs << "alternateNumber TEXT";
columnDefs << "alternateCount INTEGER";
columnDefs << "languageISO TEXT";
columnDefs << "seriesGroup TEXT";
columnDefs << "mainCharacterOrTeam TEXT";
columnDefs << "review TEXT";
columnDefs << "tags TEXT";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
columnDefs << "lastTimeMetadataSet INTEGER DEFAULT 0";
QSqlQuery updateTypeQueryToManga(db);
updateTypeQueryToManga.prepare("UPDATE comic_info SET type = manga");
bool successMigratingManga = updateTypeQueryToManga.exec();
returnValue = returnValue && successMigratingManga;
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
QSqlQuery updateNumberQueryToBis(db);
updateNumberQueryToBis.prepare("UPDATE comic_info SET number = number + 0.5 WHERE isBis = 1");
bool successMigratingBis = updateNumberQueryToBis.exec();
returnValue = returnValue && successMigratingBis;
}
{ // folder
QStringList columnDefs;
columnDefs << "added INTEGER";
columnDefs << "updated INTEGER";
columnDefs << "type INTEGER DEFAULT 0";
bool successAddingColumns = addColumns("folder", columnDefs, db);
returnValue = returnValue && successAddingColumns;
QSqlQuery updateTypeQueryToManga(db);
updateTypeQueryToManga.prepare("UPDATE folder SET type = manga");
bool successMigratingManga = updateTypeQueryToManga.exec();
returnValue = returnValue && successMigratingManga;
}
}
// ensure that INTEGER types migrated to TEXT are actually changed in the table definition to avoid internal type castings, this happened in 9.13 but a migration wasn't shipped with that version.
if (pre9_14) {
{
bool pre9_14_successfulMigration = true;
QSqlQuery pragmaFKOFF(db);
pragmaFKOFF.prepare("PRAGMA foreign_keys=OFF");
pre9_14_successfulMigration = pre9_14_successfulMigration && pragmaFKOFF.exec();
pre9_14_successfulMigration = pre9_14_successfulMigration && createComicInfoTable(db, "comic_info_migration");
QSqlQuery copyComicInfoToComicInfoMigration(db);
copyComicInfoToComicInfoMigration.prepare("INSERT INTO comic_info_migration SELECT * FROM comic_info");
pre9_14_successfulMigration = pre9_14_successfulMigration && copyComicInfoToComicInfoMigration.exec();
QSqlQuery dropComicInfo(db);
dropComicInfo.prepare("DROP TABLE comic_info");
pre9_14_successfulMigration = pre9_14_successfulMigration && dropComicInfo.exec();
QSqlQuery renameComicInfoMigrationToComicInfo(db);
renameComicInfoMigrationToComicInfo.prepare("ALTER TABLE comic_info_migration RENAME TO comic_info");
pre9_14_successfulMigration = pre9_14_successfulMigration && renameComicInfoMigrationToComicInfo.exec();
QSqlQuery pragmaFKON1("PRAGMA foreign_keys=ON", db);
returnValue = returnValue && pre9_14_successfulMigration;
}
}
if (pre9_16) {
{ // comic_info
QStringList columnDefs;
columnDefs << "imageFiltersJson TEXT";
columnDefs << "lastTimeImageFiltersSet INTEGER DEFAULT 0";
columnDefs << "lastTimeCoverSet INTEGER DEFAULT 0";
columnDefs << "usesExternalCover BOOLEAN DEFAULT 0";
columnDefs << "lastTimeMetadataSet INTEGER DEFAULT 0";
bool successAddingColumns = addColumns("comic_info", columnDefs, db);
returnValue = returnValue && successAddingColumns;
}
}
if (returnValue) {
QSqlQuery updateVersion(db);
updateVersion.prepare("UPDATE db_info SET "
"version = :version");
updateVersion.bindValue(":version", DB_VERSION);
updateVersion.exec();
returnValue = updateVersion.numRowsAffected() > 0;
}
}
if (returnValue) {
QSqlQuery updateVersion(db);
updateVersion.prepare("UPDATE db_info SET "
"version = :version");
updateVersion.bindValue(":version", DB_VERSION);
updateVersion.exec();
returnValue = updateVersion.numRowsAffected() > 0;
if (!db.commit()) {
QLOG_ERROR() << "Failed to commit transaction for database update";
returnValue = false;
}
} else {
db.rollback();
}
}
connectionName = db.connectionName();