/* * 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/Release.hpp" #include #include "core/PartialDateTime.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Directory.hpp" #include "database/Image.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" #include "EnumSetTraits.hpp" #include "IdTypeTraits.hpp" #include "PartialDateTimeTraits.hpp" #include "SqlQuery.hpp" #include "StringViewTraits.hpp" #include "Utils.hpp" 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::LastWritten || 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.clusters.size() == 1 || params.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.mediaLibrary.isValid()) query.where("t.media_library_id = ?").bind(params.mediaLibrary); 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::escapeLikeKeyword(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.clusters.size() == 1) { query.join("track_cluster t_c ON t_c.track_id = t.id") .where("t_c.cluster_id = ?") .bind(params.clusters.front()); } else if (params.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.clusters) { clusterClause.Or(WhereClause("t_c.cluster_id = ?")); query.bind(clusterId); } oss << " " << clusterClause.get(); oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")"; query.where(oss.str()); } 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::ArtistNameThenName: query.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE"); break; case ReleaseSortMethod::Random: query.orderBy("RANDOM()"); break; case ReleaseSortMethod::LastWritten: query.orderBy("t.file_last_write 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 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