Added starred entries migration

This commit is contained in:
emeric
2022-01-23 20:19:27 +01:00
parent 4851581975
commit 9b8e2737e4
+236 -68
View File
@@ -73,58 +73,43 @@ namespace Database::Migration
Db& _db; Db& _db;
}; };
static
void void
doDbMigration(Session& session) migrateFromV5(Session& session)
{
static const std::string outdatedMsg {"Outdated database, please rebuild it (delete the .db file and restart)"};
ScopedNoForeignKeys noPragmaKeys {session.getDb()};
while (1)
{
auto uniqueTransaction {session.createUniqueTransaction()};
Version version;
try
{
version = VersionInfo::getOrCreate(session)->getVersion();
LMS_LOG(DB, INFO) << "Database version = " << version << ", LMS binary version = " << LMS_DATABASE_VERSION;
if (version == LMS_DATABASE_VERSION)
{
LMS_LOG(DB, DEBUG) << "Lms database version " << LMS_DATABASE_VERSION << ": up to date!";
return;
}
}
catch (std::exception& e)
{
LMS_LOG(DB, ERROR) << "Cannot get database version info: " << e.what();
throw LmsException {outdatedMsg};
}
LMS_LOG(DB, INFO) << "Migrating database from version " << version << "...";
if (version == 5)
{ {
session.getDboSession().execute("DELETE FROM auth_token"); // format has changed session.getDboSession().execute("DELETE FROM auth_token"); // format has changed
} }
else if (version == 6)
static
void
migrateFromV6(Session& session)
{ {
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 7)
static
void
migrateFromV7(Session& session)
{ {
session.getDboSession().execute("DROP TABLE similarity_settings"); session.getDboSession().execute("DROP TABLE similarity_settings");
session.getDboSession().execute("DROP TABLE similarity_settings_feature"); session.getDboSession().execute("DROP TABLE similarity_settings_feature");
session.getDboSession().execute("ALTER TABLE scan_settings ADD similarity_engine_type INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(ScanSettings::RecommendationEngineType::Clusters)) + ")"); session.getDboSession().execute("ALTER TABLE scan_settings ADD similarity_engine_type INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(ScanSettings::RecommendationEngineType::Clusters)) + ")");
} }
else if (version == 8)
static
void
migrateFromV8(Session& session)
{ {
// Better cover handling, need to rescan the whole files // Better cover handling, need to rescan the whole files
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 9)
static
void
migrateFromV9(Session& session)
{ {
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
CREATE TABLE IF NOT EXISTS "track_bookmark" ( CREATE TABLE IF NOT EXISTS "track_bookmark" (
@@ -138,40 +123,61 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" (
constraint "fk_track_bookmark_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred constraint "fk_track_bookmark_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
);)"); );)");
} }
else if (version == 10)
static
void
migrateFromV10(Session& session)
{ {
ScanSettings::get(session).modify()->addAudioFileExtension(".m4b"); ScanSettings::get(session).modify()->addAudioFileExtension(".m4b");
ScanSettings::get(session).modify()->addAudioFileExtension(".alac"); ScanSettings::get(session).modify()->addAudioFileExtension(".alac");
} }
else if (version == 11)
static
void
migrateFromV11(Session& session)
{ {
// Sanitize bad MBID, need to rescan the whole files // Sanitize bad MBID, need to rescan the whole files
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 12)
static
void
migrateFromV12(Session& session)
{ {
// Artist and release that have a badly parsed name but a MBID had no chance to updat the name // Artist and release that have a badly parsed name but a MBID had no chance to updat the name
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 13)
static
void
migrateFromV13(Session& session)
{ {
// Always store UUID in lower case + better WMA parsing // Always store UUID in lower case + better WMA parsing
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 14)
static
void
migrateFromV14(Session& session)
{ {
// SortName now set from metadata // SortName now set from metadata
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 15)
static
void
migrateFromV15(Session& session)
{ {
session.getDboSession().execute("ALTER TABLE user ADD ui_theme INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(User::defaultUITheme)) + ")"); session.getDboSession().execute("ALTER TABLE user ADD ui_theme INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(User::defaultUITheme)) + ")");
} }
else if (version == 16)
static
void
migrateFromV16(Session& session)
{ {
session.getDboSession().execute("ALTER TABLE track ADD total_disc INTEGER NOT NULL DEFAULT(0)"); session.getDboSession().execute("ALTER TABLE track ADD total_disc INTEGER NOT NULL DEFAULT(0)");
session.getDboSession().execute("ALTER TABLE track ADD total_track INTEGER NOT NULL DEFAULT(0)"); session.getDboSession().execute("ALTER TABLE track ADD total_track INTEGER NOT NULL DEFAULT(0)");
@@ -179,7 +185,10 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" (
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 17)
static
void
migrateFromV17(Session& session)
{ {
// Drop colums total_disc/total_track from release // Drop colums total_disc/total_track from release
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
@@ -196,7 +205,11 @@ CREATE TABLE "release_backup" (
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 18)
static
void
migrateFromV18(Session& session)
{ {
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
CREATE TABLE IF NOT EXISTS "subsonic_settings" ( CREATE TABLE IF NOT EXISTS "subsonic_settings" (
@@ -206,7 +219,10 @@ CREATE TABLE IF NOT EXISTS "subsonic_settings" (
"artist_list_mode" integer not null "artist_list_mode" integer not null
))"); ))");
} }
else if (version == 19)
static
void
migrateFromV19(Session& session)
{ {
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
CREATE TABLE "user_backup" ( CREATE TABLE "user_backup" (
@@ -235,11 +251,17 @@ CREATE TABLE "user_backup" (
session.getDboSession().execute("DROP TABLE user"); session.getDboSession().execute("DROP TABLE user");
session.getDboSession().execute("ALTER TABLE user_backup RENAME TO user"); session.getDboSession().execute("ALTER TABLE user_backup RENAME TO user");
} }
else if (version == 20)
static
void
migrateFromV20(Session& session)
{ {
session.getDboSession().execute("DROP TABLE subsonic_settings"); session.getDboSession().execute("DROP TABLE subsonic_settings");
} }
else if (version == 21)
static
void
migrateFromV21(Session& session)
{ {
session.getDboSession().execute("ALTER TABLE track ADD track_replay_gain REAL"); session.getDboSession().execute("ALTER TABLE track ADD track_replay_gain REAL");
session.getDboSession().execute("ALTER TABLE track ADD release_replay_gain REAL"); session.getDboSession().execute("ALTER TABLE track ADD release_replay_gain REAL");
@@ -247,43 +269,66 @@ CREATE TABLE "user_backup" (
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 22)
static
void
migrateFromV22(Session& session)
{ {
session.getDboSession().execute("ALTER TABLE track ADD disc_subtitle TEXT NOT NULL DEFAULT ''"); session.getDboSession().execute("ALTER TABLE track ADD disc_subtitle TEXT NOT NULL DEFAULT ''");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 23)
static
void
migrateFromV23(Session& session)
{ {
// Better cover detection // Better cover detection
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 24)
static
void
migrateFromV24(Session& session)
{ {
// User's AuthMode // User's AuthMode
session.getDboSession().execute("ALTER TABLE user ADD auth_mode INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*User::defaultAuthMode*/0)) + ")"); session.getDboSession().execute("ALTER TABLE user ADD auth_mode INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*User::defaultAuthMode*/0)) + ")");
} }
else if (version == 25)
static
void
migrateFromV25(Session& session)
{ {
// Better cover detection // Better cover detection
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 26)
static
void
migrateFromV26(Session& session)
{ {
// Composer, mixer, etc. support // Composer, mixer, etc. support
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 27)
static
void
migrateFromV27(Session& session)
{ {
// Composer, mixer, etc. support, now fallback on MBID tagged entries as there is no mean to provide MBID by tags for these kinf od artists // Composer, mixer, etc. support, now fallback on MBID tagged entries as there is no mean to provide MBID by tags for these kinf od artists
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 28)
static
void
migrateFromV28(Session& session)
{ {
// Drop Auth mode // Drop Auth mode
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
@@ -308,7 +353,10 @@ CREATE TABLE "user_backup" (
session.getDboSession().execute("DROP TABLE user"); session.getDboSession().execute("DROP TABLE user");
session.getDboSession().execute("ALTER TABLE user_backup RENAME TO user"); session.getDboSession().execute("ALTER TABLE user_backup RENAME TO user");
} }
else if (version == 29)
static
void
migrateFromV29(Session& session)
{ {
session.getDboSession().execute("ALTER TABLE tracklist_entry ADD date_time TEXT"); session.getDboSession().execute("ALTER TABLE tracklist_entry ADD date_time TEXT");
session.getDboSession().execute("ALTER TABLE user ADD listenbrainz_token TEXT"); session.getDboSession().execute("ALTER TABLE user ADD listenbrainz_token TEXT");
@@ -321,7 +369,10 @@ CREATE TABLE "user_backup" (
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 30)
static
void
migrateFromV30(Session& session)
{ {
// drop "year" and "original_year" (rescan needed to convert them into dates) // drop "year" and "original_year" (rescan needed to convert them into dates)
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
@@ -352,15 +403,14 @@ CREATE TABLE "track_backup" (
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion(); ScanSettings::get(session).modify()->incScanVersion();
} }
else if (version == 31)
{
// new star system, using dedicated ObjectSets per scrobbler
session.getDboSession().execute("DROP TABLE user_artist_starred");
session.getDboSession().execute("DROP TABLE user_release_starred");
session.getDboSession().execute("DROP TABLE user_track_starred");
static
void
migrateFromV31(Session& session)
{
// new star system, using dedicated entries per scrobbler and date time
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
CREATE TABLE IF NOT EXISTS "starred_artist" ( CREATE TABLE "starred_artist" (
"id" integer primary key autoincrement, "id" integer primary key autoincrement,
"version" integer not null, "version" integer not null,
"scrobbler" integer not null, "scrobbler" integer not null,
@@ -372,7 +422,7 @@ CREATE TABLE IF NOT EXISTS "starred_artist" (
))"); ))");
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
CREATE TABLE IF NOT EXISTS "starred_release" ( CREATE TABLE "starred_release" (
"id" integer primary key autoincrement, "id" integer primary key autoincrement,
"version" integer not null, "version" integer not null,
"scrobbler" integer not null, "scrobbler" integer not null,
@@ -384,7 +434,7 @@ CREATE TABLE IF NOT EXISTS "starred_release" (
))"); ))");
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
CREATE TABLE IF NOT EXISTS "starred_track" ( CREATE TABLE "starred_track" (
"id" integer primary key autoincrement, "id" integer primary key autoincrement,
"version" integer not null, "version" integer not null,
"scrobbler" integer not null, "scrobbler" integer not null,
@@ -395,9 +445,57 @@ CREATE TABLE IF NOT EXISTS "starred_track" (
constraint "fk_starred_track_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred constraint "fk_starred_track_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
))"); ))");
// Can't migrate using class mapping as mapping may evolve in the future
// use time_t to avoid rounding issues later
const Wt::WDateTime now {Wt::WDateTime::fromTime_t(Wt::WDateTime::currentDateTime().toTime_t())};
std::map<IdType::ValueType, Scrobbler> userScrobblers;
auto getScrobbler {[&](IdType::ValueType userId)
{
auto itScrobbler {userScrobblers.find(userId)};
if (itScrobbler != std::cend(userScrobblers))
return itScrobbler->second;
auto query {session.getDboSession().query<Scrobbler>("SELECT scrobbler FROM user WHERE id = ?").bind(userId)};
auto [itInserted, inserted] {userScrobblers.emplace(userId, query.resultValue())};
assert(inserted);
return itInserted->second;
}};
auto migrateStarEntries = [&session, &getScrobbler, &now](const std::string& colName, const std::string& oldTableName, const std::string& newTableName)
{
using UserIdObjectId = std::tuple<IdType::ValueType, IdType::ValueType>;
std::vector<UserIdObjectId> starredEntries;
auto query {session.getDboSession().query<UserIdObjectId>("SELECT user_id, " + colName + " from " + oldTableName)};
auto results {query.resultList()};
starredEntries.reserve(results.size());
for (const auto& entry : results)
starredEntries.push_back(entry);
LMS_LOG(DB, INFO) << "Found " << starredEntries.size() << " " << colName << " to migrate";
for (const auto [userId, entryId] : starredEntries)
{
session.getDboSession().execute("INSERT INTO " + newTableName + " ('version', 'scrobbler', 'date_time', '" + colName + "', 'user_id') VALUES (?, ?, ?, ?, ?)")
.bind(0)
.bind(getScrobbler(userId))
.bind(now.toString().toUTF8())
.bind(entryId)
.bind(userId);
}
session.getDboSession().execute("DROP TABLE " + oldTableName);
};
migrateStarEntries("artist_id","user_artist_starred", "starred_artist");
migrateStarEntries("release_id","user_release_starred", "starred_release");
migrateStarEntries("track_id","user_track_starred", "starred_track");
// new listen system, no longer using tracklists // new listen system, no longer using tracklists
session.getDboSession().execute(R"( session.getDboSession().execute(R"(
CREATE TABLE IF NOT EXISTS "listen" ( CREATE TABLE "listen" (
"id" integer primary key autoincrement, "id" integer primary key autoincrement,
"version" integer not null, "version" integer not null,
"date_time" text, "date_time" text,
@@ -409,11 +507,81 @@ CREATE TABLE IF NOT EXISTS "listen" (
constraint "fk_listen_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred constraint "fk_listen_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
))"); ))");
} }
else
void
doDbMigration(Session& session)
{ {
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration"; static const std::string outdatedMsg {"Outdated database, please rebuild it (delete the .db file and restart)"};
throw LmsException { LMS_DATABASE_VERSION > version ? outdatedMsg : "Server binary outdated, please upgrade it to handle this database"};
ScopedNoForeignKeys noPragmaKeys {session.getDb()};
using MigrationFunction = std::function<void(Session&)>;
const std::map<unsigned, MigrationFunction> migrationFunctions
{
{5, migrateFromV5},
{6, migrateFromV6},
{7, migrateFromV7},
{8, migrateFromV8},
{9, migrateFromV9},
{10, migrateFromV10},
{11, migrateFromV11},
{12, migrateFromV12},
{13, migrateFromV13},
{14, migrateFromV14},
{15, migrateFromV15},
{16, migrateFromV16},
{17, migrateFromV17},
{18, migrateFromV18},
{19, migrateFromV19},
{20, migrateFromV20},
{21, migrateFromV21},
{22, migrateFromV22},
{23, migrateFromV23},
{24, migrateFromV24},
{25, migrateFromV25},
{26, migrateFromV26},
{27, migrateFromV27},
{28, migrateFromV28},
{29, migrateFromV29},
{30, migrateFromV30},
{31, migrateFromV31},
};
while (1)
{
auto uniqueTransaction {session.createUniqueTransaction()};
Version version;
try
{
version = VersionInfo::getOrCreate(session)->getVersion();
LMS_LOG(DB, INFO) << "Database version = " << version << ", LMS binary version = " << LMS_DATABASE_VERSION;
} }
catch (std::exception& e)
{
LMS_LOG(DB, ERROR) << "Cannot get database version info: " << e.what();
throw LmsException {outdatedMsg};
}
if (version == LMS_DATABASE_VERSION)
{
LMS_LOG(DB, DEBUG) << "Lms database version " << LMS_DATABASE_VERSION << ": up to date!";
return;
}
else if (version > LMS_DATABASE_VERSION)
{
throw LmsException {"Server binary outdated, please upgrade it to handle this database"};
}
if (version < migrationFunctions.begin()->first)
throw LmsException {outdatedMsg};
LMS_LOG(DB, INFO) << "Migrating database from version " << version << "...";
auto itMigrationFunc {migrationFunctions.find(version)};
assert(itMigrationFunc != std::cend(migrationFunctions));
itMigrationFunc->second(session);
VersionInfo::get(session).modify()->setVersion(++version); VersionInfo::get(session).modify()->setVersion(++version);
} }