/* * Copyright (C) 2015 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 "database/objects/Release.hpp" #include #include #include "core/PartialDateTime.hpp" #include "database/Session.hpp" #include "database/Types.hpp" #include "database/objects/Artist.hpp" #include "database/objects/Artwork.hpp" #include "database/objects/Cluster.hpp" #include "database/objects/Directory.hpp" #include "database/objects/MediaLibrary.hpp" #include "database/objects/Medium.hpp" #include "database/objects/Track.hpp" #include "database/objects/TrackArtistLink.hpp" #include "database/objects/TrackEmbeddedImage.hpp" #include "database/objects/TrackEmbeddedImageLink.hpp" #include "database/objects/TrackLyrics.hpp" #include "database/objects/User.hpp" #include "SqlQuery.hpp" #include "Utils.hpp" #include "traits/EnumSetTraits.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PartialDateTimeTraits.hpp" #include "traits/StringViewTraits.hpp" DBO_INSTANTIATE_TEMPLATES(lms::db::Country) DBO_INSTANTIATE_TEMPLATES(lms::db::Label) DBO_INSTANTIATE_TEMPLATES(lms::db::Release) DBO_INSTANTIATE_TEMPLATES(lms::db::ReleaseType) namespace lms::db { namespace { template Wt::Dbo::Query createQuery(Session& session, std::string_view itemToSelect, const Release::FindParameters& params) { assert(params.keywords.empty() || params.name.empty()); assert(!params.directory.isValid() || !params.parentDirectory.isValid()); auto query{ session.getDboSession()->query("SELECT " + std::string{ itemToSelect } + " from release r") }; if (params.sortMethod == ReleaseSortMethod::ArtistNameThenName || params.sortMethod == ReleaseSortMethod::LastWrittenDesc || params.sortMethod == ReleaseSortMethod::AddedDesc || params.sortMethod == ReleaseSortMethod::DateAsc || params.sortMethod == ReleaseSortMethod::DateDesc || params.sortMethod == ReleaseSortMethod::OriginalDate || params.sortMethod == ReleaseSortMethod::OriginalDateDesc || params.writtenAfter.isValid() || params.dateRange || params.artist.isValid() || params.filters.clusters.size() == 1 || params.filters.mediaLibrary.isValid() || params.directory.isValid() || params.parentDirectory.isValid()) { query.join("track t ON t.release_id = r.id"); } if (params.parentDirectory.isValid()) { query.join("directory d ON t.directory_id = d.id"); query.where("d.parent_directory_id = ?").bind(params.parentDirectory); } if (params.filters.mediaLibrary.isValid()) query.where("t.media_library_id = ?").bind(params.filters.mediaLibrary); if (params.filters.label.isValid()) { query.join("release_label r_l ON r_l.release_id = r.id"); query.where("r_l.label_id = ?").bind(params.filters.label); } if (params.filters.releaseType.isValid()) { query.join("release_release_type r_r_t ON r_r_t.release_id = r.id"); query.where("r_r_t.release_type_id = ?").bind(params.filters.releaseType); } if (params.directory.isValid()) query.where("t.directory_id = ?").bind(params.directory); if (!params.releaseType.empty()) { query.join("release_release_type r_r_t ON r_r_t.release_id = r.id"); query.join("release_type r_t ON r_t.id = r_r_t.release_type_id") .where("r_t.name = ?") .bind(params.releaseType); } if (params.writtenAfter.isValid()) query.where("t.file_last_write > ?").bind(params.writtenAfter); if (params.dateRange) { query.where("CAST(SUBSTR(t.date, 1, 4) AS INTEGER) >= ?").bind(params.dateRange->begin); query.where("CAST(SUBSTR(t.date, 1, 4) AS INTEGER) <= ?").bind(params.dateRange->end); } if (!params.name.empty()) query.where("r.name = ?").bind(params.name); for (std::string_view keyword : params.keywords) query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeForLikeKeyword(keyword) + "%"); if (params.starringUser.isValid()) { assert(params.feedbackBackend); query.join("starred_release s_r ON s_r.release_id = r.id") .where("s_r.user_id = ?") .bind(params.starringUser) .where("s_r.backend = ?") .bind(*params.feedbackBackend) .where("s_r.sync_state <> ?") .bind(SyncState::PendingRemove); } if (params.artist.isValid() || params.sortMethod == ReleaseSortMethod::ArtistNameThenName) { query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id"); if (params.artist.isValid()) query.where("t_a_l.artist_id = ?").bind(params.artist); if (params.sortMethod == ReleaseSortMethod::ArtistNameThenName) query.join("artist a ON a.id = t_a_l.artist_id"); if (!params.trackArtistLinkTypes.empty()) { std::ostringstream oss; bool first{ true }; for (TrackArtistLinkType linkType : params.trackArtistLinkTypes) { if (!first) oss << " OR "; oss << "t_a_l.type = ?"; query.bind(linkType); first = false; } query.where(oss.str()); } if (!params.excludedTrackArtistLinkTypes.empty()) { std::ostringstream oss; oss << "r.id NOT IN (SELECT DISTINCT r.id FROM release r" " INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id" " INNER JOIN track t ON t.release_id = r.id" " WHERE (t_a_l.artist_id = ? AND ("; query.bind(params.artist); bool first{ true }; for (const TrackArtistLinkType linkType : params.excludedTrackArtistLinkTypes) { if (!first) oss << " OR "; oss << "t_a_l.type = ?"; query.bind(linkType); first = false; } oss << ")))"; query.where(oss.str()); } } if (params.filters.clusters.size() == 1) { query.join("track_cluster t_c ON t_c.track_id = t.id") .where("t_c.cluster_id = ?") .bind(params.filters.clusters.front()); } else if (params.filters.clusters.size() > 1) { std::ostringstream oss; oss << "r.id IN (SELECT DISTINCT t.release_id FROM track t" " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; WhereClause clusterClause; for (const ClusterId clusterId : params.filters.clusters) { clusterClause.Or(WhereClause("t_c.cluster_id = ?")); query.bind(clusterId); } oss << " " << clusterClause.get(); oss << " GROUP BY t.id HAVING COUNT(*) = " << params.filters.clusters.size() << ")"; query.where(oss.str()); } if (params.releaseGroupMBID) query.where("group_mbid = ?").bind(params.releaseGroupMBID->getAsString()); switch (params.sortMethod) { case ReleaseSortMethod::None: break; case ReleaseSortMethod::Id: query.orderBy("r.id"); break; case ReleaseSortMethod::Name: query.orderBy("r.name COLLATE NOCASE"); break; case ReleaseSortMethod::SortName: query.orderBy("r.sort_name COLLATE NOCASE"); break; case ReleaseSortMethod::ArtistNameThenName: query.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE"); break; case ReleaseSortMethod::Random: query.orderBy("RANDOM()"); break; case ReleaseSortMethod::LastWrittenDesc: query.orderBy("t.file_last_write DESC"); break; case ReleaseSortMethod::AddedDesc: query.orderBy("t.file_added DESC"); break; case ReleaseSortMethod::DateAsc: query.orderBy("t.date ASC, r.name COLLATE NOCASE"); break; case ReleaseSortMethod::DateDesc: query.orderBy("t.date DESC, r.name COLLATE NOCASE"); break; case ReleaseSortMethod::OriginalDate: query.orderBy("COALESCE(t.original_date, t.date), r.name COLLATE NOCASE"); break; case ReleaseSortMethod::OriginalDateDesc: query.orderBy("COALESCE(t.original_date, t.date) DESC, r.name COLLATE NOCASE"); break; case ReleaseSortMethod::StarredDateDesc: assert(params.starringUser.isValid()); query.orderBy("s_r.date_time DESC"); break; } return query; } template Wt::Dbo::Query createArtistQuery(Wt::Dbo::Session& session, std::string_view itemToSelect, ReleaseId releaseId, TrackArtistLinkType linkType) { auto query{ session.query("SELECT " + std::string{ itemToSelect } + " from artist a") .join("track_artist_link t_a_l ON t_a_l.artist_id = a.id") .join("track t ON t.id = t_a_l.track_id") .where("t.release_id = ?") .bind(releaseId) .where("+t_a_l.type = ?") .bind(linkType) // adding + since the query planner does not a good job when analyze is not performed .groupBy("a.id") .orderBy("t_a_l.id") }; return query; }; } // namespace Country::Country(std::string_view name) : _name{ name } { // As we use the name to uniquely identoify release type, we must throw (and not truncate) if (name.size() > _maxNameLength) throw Exception{ "Country name is too long: " + std::string{ name } + "'" }; } Country::pointer Country::create(Session& session, std::string_view name) { return session.getDboSession()->add(std::unique_ptr{ new Country{ name } }); } std::size_t Country::getCount(Session& session) { return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) FROM country")); } Country::pointer Country::find(Session& session, CountryId id) { session.checkReadTransaction(); return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT c from country c").where("c.id = ?").bind(id)); } Country::pointer Country::find(Session& session, std::string_view name) { session.checkReadTransaction(); if (name.size() > _maxNameLength) throw Exception{ "Requeted Country name is too long: " + std::string{ name } + "'" }; return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT c from country c").where("c.name = ?").bind(name)); } RangeResults Country::findOrphanIds(Session& session, std::optional range) { session.checkReadTransaction(); // select the labels that have no releases auto query{ session.getDboSession()->query("select c.id from country c LEFT OUTER JOIN release_country r_c ON c.id = r_c.country_id WHERE r_c.release_id IS NULL") }; return utils::execRangeQuery(query, range); } Label::Label(std::string_view name) : _name{ name } { // As we use the name to uniquely identoify release type, we must throw (and not truncate) if (name.size() > _maxNameLength) throw Exception{ "Label name is too long: " + std::string{ name } + "'" }; } Label::pointer Label::create(Session& session, std::string_view name) { return session.getDboSession()->add(std::unique_ptr