/* * Copyright (C) 2020 Emeric Poupon * * This file is part of LMS. * * LMS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * LMS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with LMS. If not, see . */ #include "services/database/Session.hpp" #include #include "utils/Exception.hpp" #include "utils/ILogger.hpp" #include "services/database/Artist.hpp" #include "services/database/AuthToken.hpp" #include "services/database/Cluster.hpp" #include "services/database/Db.hpp" #include "services/database/Listen.hpp" #include "services/database/Release.hpp" #include "services/database/ScanSettings.hpp" #include "services/database/StarredArtist.hpp" #include "services/database/StarredRelease.hpp" #include "services/database/StarredTrack.hpp" #include "services/database/Track.hpp" #include "services/database/TrackBookmark.hpp" #include "services/database/TrackArtistLink.hpp" #include "services/database/TrackList.hpp" #include "services/database/TrackFeatures.hpp" #include "services/database/TransactionChecker.hpp" #include "services/database/User.hpp" #include "EnumSetTraits.hpp" #include "Migration.hpp" namespace Database { WriteTransaction::WriteTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session) : _lock{ mutex }, _transaction{ session } { TransactionChecker::pushWriteTransaction(_transaction.session()); } WriteTransaction::~WriteTransaction() { TransactionChecker::popWriteTransaction(_transaction.session()); } ReadTransaction::ReadTransaction(Wt::Dbo::Session& session) : _transaction{ session } { TransactionChecker::pushReadTransaction(_transaction.session()); } ReadTransaction::~ReadTransaction() { TransactionChecker::popReadTransaction(_transaction.session()); } Session::Session(Db& db) : _db{ db } { _session.setConnectionPool(_db.getConnectionPool()); _session.mapClass("version_info"); _session.mapClass("artist"); _session.mapClass("auth_token"); _session.mapClass("cluster"); _session.mapClass("cluster_type"); _session.mapClass("listen"); _session.mapClass("release"); _session.mapClass("scan_settings"); _session.mapClass("starred_artist"); _session.mapClass("starred_release"); _session.mapClass("starred_track"); _session.mapClass("track"); _session.mapClass("track_bookmark"); _session.mapClass("track_artist_link"); _session.mapClass("track_features"); _session.mapClass("tracklist"); _session.mapClass("tracklist_entry"); _session.mapClass("user"); } WriteTransaction Session::createWriteTransaction() { return WriteTransaction{ _db.getMutex(), _session }; } ReadTransaction Session::createReadTransaction() { return ReadTransaction{ _session }; } void Session::prepareTables() { LMS_LOG(DB, INFO, "Preparing tables..."); // Initial creation case try { _session.createTables(); LMS_LOG(DB, INFO, "Tables created"); } catch (Wt::Dbo::Exception& e) { LMS_LOG(DB, DEBUG, "Cannot create tables: " << e.what()); if (std::string_view{ e.what() }.find("already exists") == std::string_view::npos) { LMS_LOG(DB, ERROR, "Cannot create tables: " << e.what()); throw e; } } Migration::doDbMigration(*this); // Indexes { auto transaction{ createWriteTransaction() }; _session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)"); _session.execute("CREATE INDEX IF NOT EXISTS artist_sort_name_nocase_idx ON artist(sort_name COLLATE NOCASE)"); _session.execute("CREATE INDEX IF NOT EXISTS artist_mbid_idx ON artist(mbid)"); _session.execute("CREATE INDEX IF NOT EXISTS auth_token_user_idx ON auth_token(user_id)"); _session.execute("CREATE INDEX IF NOT EXISTS auth_token_expiry_idx ON auth_token(expiry)"); _session.execute("CREATE INDEX IF NOT EXISTS auth_token_value_idx ON auth_token(value)"); _session.execute("CREATE INDEX IF NOT EXISTS cluster_name_idx ON cluster(name)"); _session.execute("CREATE INDEX IF NOT EXISTS cluster_cluster_type_idx ON cluster(cluster_type_id)"); _session.execute("CREATE INDEX IF NOT EXISTS cluster_type_name_idx ON cluster_type(name)"); _session.execute("CREATE INDEX IF NOT EXISTS release_name_idx ON release(name)"); _session.execute("CREATE INDEX IF NOT EXISTS release_name_nocase_idx ON release(name COLLATE NOCASE)"); _session.execute("CREATE INDEX IF NOT EXISTS release_mbid_idx ON release(mbid)"); _session.execute("CREATE INDEX IF NOT EXISTS track_path_idx ON track(file_path)"); _session.execute("CREATE INDEX IF NOT EXISTS track_name_idx ON track(name)"); _session.execute("CREATE INDEX IF NOT EXISTS track_name_nocase_idx ON track(name COLLATE NOCASE)"); _session.execute("CREATE INDEX IF NOT EXISTS track_mbid_idx ON track(mbid)"); _session.execute("CREATE INDEX IF NOT EXISTS track_recording_mbid_idx ON track(recording_mbid)"); _session.execute("CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)"); _session.execute("CREATE INDEX IF NOT EXISTS track_file_last_write_idx ON track(file_last_write)"); _session.execute("CREATE INDEX IF NOT EXISTS track_date_idx ON track(date)"); _session.execute("CREATE INDEX IF NOT EXISTS track_original_date_idx ON track(original_date)"); _session.execute("CREATE INDEX IF NOT EXISTS tracklist_name_idx ON tracklist(name)"); _session.execute("CREATE INDEX IF NOT EXISTS tracklist_user_idx ON tracklist(user_id)"); _session.execute("CREATE INDEX IF NOT EXISTS track_features_track_idx ON track_features(track_id)"); _session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_artist_idx ON track_artist_link(artist_id)"); _session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_track_idx ON track_artist_link(track_id)"); _session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_type_idx ON track_artist_link(type)"); _session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_artist_type_idx ON track_artist_link(artist_id,type)"); _session.execute("CREATE INDEX IF NOT EXISTS track_bookmark_user_idx ON track_bookmark(user_id)"); _session.execute("CREATE INDEX IF NOT EXISTS track_bookmark_user_track_idx ON track_bookmark(user_id,track_id)"); _session.execute("CREATE INDEX IF NOT EXISTS listen_backend_idx ON listen(backend)"); _session.execute("CREATE INDEX IF NOT EXISTS listen_user_backend_idx ON listen(user_id,backend)"); _session.execute("CREATE INDEX IF NOT EXISTS listen_track_user_backend_idx ON listen(track_id,user_id,backend)"); _session.execute("CREATE INDEX IF NOT EXISTS listen_user_track_backend_date_time_idx ON listen(user_id,track_id,backend,date_time)"); _session.execute("CREATE INDEX IF NOT EXISTS starred_artist_user_backend_idx ON starred_artist(user_id,backend)"); _session.execute("CREATE INDEX IF NOT EXISTS starred_artist_artist_user_backend_idx ON starred_artist(artist_id,user_id,backend)"); _session.execute("CREATE INDEX IF NOT EXISTS starred_release_user_backend_idx ON starred_release(user_id,backend)"); _session.execute("CREATE INDEX IF NOT EXISTS starred_release_release_user_backend_idx ON starred_release(release_id,user_id,backend)"); _session.execute("CREATE INDEX IF NOT EXISTS starred_track_user_backend_idx ON starred_track(user_id,backend)"); _session.execute("CREATE INDEX IF NOT EXISTS starred_track_track_user_backend_idx ON starred_track(track_id,user_id,backend)"); } } void Session::analyze() { LMS_LOG(DB, INFO, "Analyzing database..."); { auto transaction{ createWriteTransaction() }; _session.execute("ANALYZE"); } LMS_LOG(DB, INFO, "Database Analyze complete"); } void Session::optimize() { LMS_LOG(DB, INFO, "Optimizing database..."); { auto transaction{ createWriteTransaction() }; _session.execute("PRAGMA optimize"); } LMS_LOG(DB, INFO, "Database optimizing complete"); } } // namespace Database