New UI layout and UI improvements. Fixes #50

This commit is contained in:
emeric
2020-05-10 13:11:43 +02:00
parent 8277b9c82b
commit 74fc4e058e
79 changed files with 3003 additions and 1987 deletions
+151 -67
View File
@@ -74,91 +74,133 @@ Artist::create(Session& session, const std::string& name, const std::optional<UU
return res;
}
template <typename T>
static
Wt::Dbo::Query<Artist::pointer>
getQuery(Session& session,
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords,
std::optional<TrackArtistLink::Type> linkType,
Artist::SortMethod sortMethod)
std::optional<TrackArtistLink::Type> linkType)
{
session.checkSharedLocked();
WhereClause where;
auto query {session.getDboSession().query<T>(queryStr)};
query.join("track t ON t.id = t_a_l.track_id");
query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id");
std::ostringstream oss;
oss << "SELECT DISTINCT a FROM artist a";
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
if (!keywords.empty())
{
WhereClause whereKeywordsName;
WhereClause whereKeywordsSortName;
std::vector<std::string> clauses;
std::vector<std::string> sortClauses;
for (auto keyword : keywords)
for (const std::string& keyword : keywords)
{
whereKeywordsName.And(WhereClause("a.name LIKE ?")).bind("%%" + keyword + "%%");
whereKeywordsSortName.And(WhereClause("a.sort_name LIKE ?")).bind("%%" + keyword + "%%");
clauses.push_back("a.name LIKE ?");
query.bind("%%" + keyword + "%%");
sortClauses.push_back("a.sort_name LIKE ?");
query.bind("%%" + keyword + "%%");
}
where.And(whereKeywordsName.Or(whereKeywordsSortName));
query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")");
}
if (!clusterIds.empty() || linkType)
{
oss << " INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id";
if (!clusterIds.empty())
{
oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (auto id : clusterIds)
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
where.And(clusterClause);
}
if (linkType)
where.And(WhereClause {"t_a_l.type = ?"}.bind(std::to_string(static_cast<int>(*linkType))));
}
oss << " " << where.get();
if (!clusterIds.empty())
oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size();
switch (sortMethod)
{
case Artist::SortMethod::None:
break;
case Artist::SortMethod::ByName:
oss << " ORDER BY a.name COLLATE NOCASE";
break;
case Artist::SortMethod::BySortName:
oss << " ORDER BY a.sort_name COLLATE NOCASE";
break;
}
std::ostringstream oss;
oss << "a.id IN (SELECT DISTINCT a.id FROM artist a"
" INNER JOIN track t ON t.id = t_a_l.track_id"
" INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
Wt::Dbo::Query<Artist::pointer> query = session.getDboSession().query<Artist::pointer>( oss.str() );
WhereClause clusterClause;
for (const IdType clusterId : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
}
for (const std::string& bindArg : where.getBindArgs())
{
query.bind(bindArg);
oss << " " << clusterClause.get();
oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
query.where(oss.str());
}
return query;
}
std::vector<Artist::pointer>
Artist::getAll(Session& session, SortMethod sortMethod, std::optional<std::size_t> offset, std::optional<std::size_t> size)
Artist::getAll(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<Artist::pointer> res = getQuery(session, {}, {}, std::nullopt, sortMethod)
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Artist>();
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getAll(Session& session, SortMethod sortMethod)
{
session.checkSharedLocked();
auto query {session.getDboSession().find<Artist>()};
switch (sortMethod)
{
case Artist::SortMethod::None:
break;
case Artist::SortMethod::ByName:
query.orderBy("name COLLATE NOCASE");
break;
case Artist::SortMethod::BySortName:
query.orderBy("sort_name COLLATE NOCASE");
break;
}
Wt::Dbo::collection<pointer> res = query;
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getAll(Session& session, SortMethod sortMethod, std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Artist::pointer>(session, "SELECT a FROM Artist a", {}, {}, std::nullopt)};
switch (sortMethod)
{
case Artist::SortMethod::None:
break;
case Artist::SortMethod::ByName:
query.orderBy("a.name COLLATE NOCASE");
break;
case Artist::SortMethod::BySortName:
query.orderBy("a.sort_name COLLATE NOCASE");
break;
}
Wt::Dbo::collection<Artist::pointer> collection = query
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<IdType>
Artist::getAllIds(Session& session)
{
@@ -168,6 +210,21 @@ Artist::getAllIds(Session& session)
return std::vector<IdType>(res.begin(), res.end());
}
std::vector<IdType>
Artist::getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLink::Type> linkType, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto query {createQuery<IdType>(session, "SELECT DISTINCT a.id from artist a", clusters, {}, linkType)};
Wt::Dbo::collection<IdType> res = query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
return std::vector<IdType>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getAllOrphans(Session& session)
{
@@ -197,8 +254,8 @@ Artist::getByClusters(Session& session, const std::set<IdType>& clusters, SortMe
assert(!clusters.empty());
session.checkSharedLocked();
bool more;
return getByFilter(session, clusters, {}, {}, sortMethod, {}, {}, more);
bool more{};
return getByFilter(session, clusters, {}, std::nullopt, sortMethod, std::nullopt, more);
}
std::vector<Artist::pointer>
@@ -207,18 +264,31 @@ Artist::getByFilter(Session& session,
const std::vector<std::string>& keywords,
std::optional<TrackArtistLink::Type> linkType,
SortMethod sortMethod,
std::optional<std::size_t> offset,
std::optional<std::size_t> size,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<Artist::pointer> collection = getQuery(session, clusters, keywords, linkType, sortMethod)
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
auto query {createQuery<Artist::pointer>(session, "SELECT DISTINCT a from artist a", clusters, keywords, linkType)};
switch (sortMethod)
{
case Artist::SortMethod::None:
break;
case Artist::SortMethod::ByName:
query.orderBy("a.name COLLATE NOCASE");
break;
case Artist::SortMethod::BySortName:
query.orderBy("a.sort_name COLLATE NOCASE");
break;
}
Wt::Dbo::collection<Artist::pointer> collection = query
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
if (size && res.size() == static_cast<std::size_t>(*size) + 1)
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
@@ -230,14 +300,29 @@ Artist::getByFilter(Session& session,
}
std::vector<Artist::pointer>
Artist::getLastWritten(Session& session, Wt::WDateTime after, std::optional<std::size_t> limit)
Artist::getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusters, std::optional<TrackArtistLink::Type> linkType, std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<Artist::pointer> res = session.getDboSession().query<Artist::pointer>("SELECT a from artist a INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id INNER JOIN track t ON t.id = t_a_l.track_id")
.where("t.file_last_write > ?").bind(after)
.groupBy("a.id")
auto query {createQuery<Artist::pointer>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
if (after)
query.where("t.file_last_write > ?").bind(*after);
Wt::Dbo::collection<Artist::pointer> collection = query
.orderBy("t.file_last_write DESC")
.limit(limit ? static_cast<int>(*limit) : -1);
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return std::vector<pointer>(res.begin(), res.end());
}
@@ -427,7 +512,6 @@ void
Artist::setSortName(const std::string& sortName)
{
_sortName = std::string(sortName, 0 , _maxNameLength);
LMS_LOG(DB, DEBUG) << "SORT NAME = '" << _sortName << "'";
}
} // namespace Database
+18 -2
View File
@@ -46,9 +46,25 @@ Db::Db(const std::filesystem::path& dbPath)
void
Db::executeSql(const std::string& sql)
{
auto connection {_connectionPool->getConnection()};
ScopedConnection connection {*_connectionPool};
connection->executeSql(sql);
_connectionPool->returnConnection(std::move(connection));
}
Db::ScopedConnection::ScopedConnection(Wt::Dbo::SqlConnectionPool& pool)
: _connectionPool {pool}
, _connection {_connectionPool.getConnection()}
{
}
Db::ScopedConnection::~ScopedConnection()
{
_connectionPool.returnConnection(std::move(_connection));
}
Wt::Dbo::SqlConnection* Db::ScopedConnection::operator->() const
{
return _connection.get();
}
} // namespace Database
+95 -63
View File
@@ -31,6 +31,45 @@
namespace Database
{
template <typename T>
static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords)
{
auto query {session.getDboSession().query<T>(queryStr)};
query.join("track t ON t.release_id = r.id");
for (const std::string& keyword : keywords)
query.where("r.name LIKE ?").bind("%%" + keyword + "%%");
if (!clusterIds.empty())
{
std::ostringstream oss;
oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
" INNER JOIN track t ON t.release_id = r.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (const IdType clusterId : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")";
query.where(oss.str());
}
return query;
}
Release::Release(const std::string& name, const std::optional<UUID>& MBID)
: _name {std::string(name, 0 , _maxNameLength)},
_MBID {MBID ? MBID->getAsString() : ""}
@@ -84,13 +123,13 @@ Release::getCount(Session& session)
}
std::vector<Release::pointer>
Release::getAll(Session& session, std::optional<std::size_t> offset, std::optional<std::size_t> size)
Release::getAll(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Release>()
.offset(offset ? static_cast<int>(*offset) : -1)
.limit(size ? static_cast<int>(*size) : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) : -1)
.orderBy("name COLLATE NOCASE");
return std::vector<pointer>(res.begin(), res.end());
@@ -123,17 +162,34 @@ Release::getAllOrderedByArtist(Session& session, std::optional<std::size_t> offs
}
std::vector<Release::pointer>
Release::getAllRandom(Session& session, std::optional<std::size_t> size)
Release::getAllRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> size)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> res = session.getDboSession().find<Release>()
.limit(size ? static_cast<int>(*size) : -1)
.orderBy("RANDOM()");
auto query {createQuery<Release::pointer>(session, "SELECT DISTINCT r from release r", clusterIds,{})};
Wt::Dbo::collection<pointer> res = query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<IdType>
Release::getAllIdsRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto query {createQuery<IdType>(session, "SELECT DISTINCT r.id from release r", clusterIds,{})};
Wt::Dbo::collection<IdType> res = query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
return std::vector<IdType>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getAllOrphans(Session& session)
{
@@ -145,18 +201,34 @@ Release::getAllOrphans(Session& session)
}
std::vector<Release::pointer>
Release::getLastWritten(Session& session, const Wt::WDateTime& after, std::optional<std::size_t> offset, std::optional<std::size_t> limit)
Release::getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusterIds,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<Release::pointer> res = session.getDboSession().query<Release::pointer>("SELECT r from release r INNER JOIN track t ON r.id = t.release_id")
.where("t.file_last_write > ?").bind(after)
.groupBy("r.id")
.orderBy("t.file_last_write DESC")
.offset(offset ? static_cast<int>(*offset) : -1)
.limit(limit ? static_cast<int>(*limit) : -1);
auto query {createQuery<Release::pointer>(session, "SELECT r from release r", clusterIds, {})};
if (after)
query.where("t.file_last_write > ?").bind(after);
return std::vector<pointer>(res.begin(), res.end());
Wt::Dbo::collection<Release::pointer> collection = query
.orderBy("t.file_last_write DESC")
.groupBy("r.id")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Release::pointer>
@@ -173,47 +245,6 @@ Release::getByYear(Session& session, int yearFrom, int yearTo, std::optional<std
return std::vector<pointer>(res.begin(), res.end());
}
static
Wt::Dbo::Query<Release::pointer>
getQuery(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords)
{
WhereClause where;
std::ostringstream oss;
oss << "SELECT DISTINCT r FROM release r";
for (auto keyword : keywords)
where.And(WhereClause("r.name LIKE ?")).bind("%%" + keyword + "%%");
if (!clusterIds.empty())
{
oss << " INNER JOIN track t ON t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (auto id : clusterIds)
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
where.And(clusterClause);
}
oss << " " << where.get();
if (!clusterIds.empty())
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
oss << " ORDER BY r.name COLLATE NOCASE";
Wt::Dbo::Query<Release::pointer> query = session.getDboSession().query<Release::pointer>( oss.str() );
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
return query;
}
std::vector<Release::pointer>
Release::getByClusters(Session& session, const std::set<IdType>& clusters)
{
@@ -222,26 +253,27 @@ Release::getByClusters(Session& session, const std::set<IdType>& clusters)
session.checkSharedLocked();
bool moreResults;
return getByFilter(session, clusters, {}, {}, {}, moreResults);
return getByFilter(session, clusters, {}, std::nullopt, moreResults);
}
std::vector<Release::pointer>
Release::getByFilter(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords,
std::optional<std::size_t> offset,
std::optional<std::size_t> size,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = getQuery(session, clusterIds, keywords)
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
Wt::Dbo::collection<pointer> collection = createQuery<Release::pointer>(session, "SELECT r from release r", clusterIds, keywords)
.groupBy("r.id")
.orderBy("r.name COLLATE NOCASE")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
if (size && res.size() == static_cast<std::size_t>(*size) + 1)
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
+1
View File
@@ -378,6 +378,7 @@ Session::prepareTables()
_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_file_last_write_idx ON track(file_last_write)");
_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)");
+88 -67
View File
@@ -32,9 +32,46 @@
namespace Database {
template <typename T>
static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<T>(queryStr)};
for (const std::string& keyword : keywords)
query.where("t.name LIKE ?").bind("%%" + keyword + "%%");
if (!clusterIds.empty())
{
std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause;
for (const IdType clusterId : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")";
query.where(oss.str());
}
return query;
}
Track::Track(const std::filesystem::path& p)
:
_filePath( p.string() )
: _filePath {p.string()}
{
}
@@ -58,17 +95,34 @@ Track::getAll(Session& session, std::optional<std::size_t> limit)
}
std::vector<Track::pointer>
Track::getAllRandom(Session& session, std::optional<std::size_t> limit)
Track::getAllRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<Track::pointer> res {session.getDboSession().find<Track>()
.limit(limit ? static_cast<int>(*limit) : -1)
.orderBy("RANDOM()")};
auto query {createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, {})};
return std::vector<Track::pointer>(std::cbegin(res), std::cend(res));
Wt::Dbo::collection<Track::pointer> collection = query
.orderBy("RANDOM()")
.limit(limit ? static_cast<int>(*limit) + 1: -1);
return std::vector<pointer>(collection.begin(), collection.end());
}
std::vector<Database::IdType>
Track::getAllIdsRandom(Session& session, const std::set<IdType>& clusterIds, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
auto query {createQuery<IdType>(session, "SELECT t.id from track t", clusterIds, {})};
Wt::Dbo::collection<IdType> collection = query
.orderBy("RANDOM()")
.limit(limit ? static_cast<int>(*limit) + 1: -1);
return std::vector<IdType>(collection.begin(), collection.end());
}
std::vector<IdType>
Track::getAllIds(Session& session)
{
@@ -147,16 +201,30 @@ Track::getMBIDDuplicates(Session& session)
}
std::vector<Track::pointer>
Track::getLastWritten(Session& session, const Wt::WDateTime& after, std::optional<std::size_t> limit)
Track::getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<Track::pointer> res = session.getDboSession().find<Track>()
.where("file_last_write > ?").bind(after)
.orderBy("file_last_write DESC")
.limit(limit ? static_cast<int>(*limit) : -1);
auto query {createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, {})};
if (after)
query.where("t.file_last_write > ?").bind(after);
return std::vector<pointer>(res.begin(), res.end());
Wt::Dbo::collection<Track::pointer> collection = query
.orderBy("t.file_last_write DESC")
.groupBy("t.id")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1);
auto res {std::vector<pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Track::pointer>
@@ -225,66 +293,21 @@ Track::hasTrackFeatures() const
return (_trackFeatures.lock() != Database::TrackFeatures::pointer());
}
static
Wt::Dbo::Query< Track::pointer >
getQuery(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords)
{
session.checkSharedLocked();
WhereClause where;
std::ostringstream oss;
oss << "SELECT t FROM track t";
for (auto keyword : keywords)
where.And(WhereClause("t.name LIKE ?")).bind("%%" + keyword + "%%");
if (!clusterIds.empty())
{
oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (auto id : clusterIds)
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
where.And(clusterClause);
}
oss << " " << where.get();
if (!clusterIds.empty())
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
oss << " ORDER BY t.name COLLATE NOCASE";
Wt::Dbo::Query<Track::pointer> query = session.getDboSession().query<Track::pointer>( oss.str() );
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
return query;
}
std::vector<Track::pointer>
Track::getByFilter(Session& session,
const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords,
std::optional<std::size_t> offset,
std::optional<std::size_t> size,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
Wt::Dbo::collection<pointer> collection = getQuery(session, clusterIds, keywords)
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
Wt::Dbo::collection<pointer> collection = createQuery<Track::pointer>(session, "SELECT t from track t", clusterIds, keywords)
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
std::vector<pointer> res(collection.begin(), collection.end());
if (size && (res.size() == static_cast<std::size_t>(*size) + 1))
if (range && (res.size() == static_cast<std::size_t>(range->limit) + 1))
{
moreResults = true;
res.pop_back();
@@ -340,12 +363,10 @@ Track::getByClusters(Session& session,
session.checkSharedLocked();
bool moreResults;
return getByFilter(session,
clusters,
{},
{},
{},
{}, // keywords
std::nullopt, // range
moreResults);
}
+243 -30
View File
@@ -28,6 +28,7 @@
#include "database/Session.hpp"
#include "database/User.hpp"
#include "database/Track.hpp"
#include "SqlQuery.hpp"
namespace Database {
@@ -106,6 +107,23 @@ TrackList::getById(Session& session, IdType id)
return session.getDboSession().find<TrackList>().where("id = ?").bind(id);
}
std::size_t
TrackList::getCount() const
{
return _entries.size();
}
Wt::Dbo::ptr<TrackListEntry>
TrackList::getEntry(std::size_t pos) const
{
Wt::Dbo::ptr<TrackListEntry> res;
auto entries = getEntries(pos, 1);
if (!entries.empty())
res = entries.front();
return res;
}
std::vector<Wt::Dbo::ptr<TrackListEntry>>
TrackList::getEntries(std::optional<std::size_t> offset, std::optional<std::size_t> size) const
@@ -139,22 +157,180 @@ TrackList::getEntriesReverse(std::optional<std::size_t> offset, std::optional<st
return std::vector<Wt::Dbo::ptr<TrackListEntry>>(entries.begin(), entries.end());
}
Wt::Dbo::ptr<TrackListEntry>
TrackList::getEntry(std::size_t pos) const
static
Wt::Dbo::Query<Artist::pointer>
createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType)
{
Wt::Dbo::ptr<TrackListEntry> res;
auto query {session.query<Artist::pointer>(queryStr)};
query.join("track t ON t.id = t_a_l.track_id");
query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id");
query.join("tracklist_entry p_e ON p_e.track_id = t.id");
query.join("tracklist p ON p.id = p_e.tracklist_id");
auto entries = getEntries(pos, 1);
if (!entries.empty())
res = entries.front();
query.where("p.id = ?").bind(tracklistId);
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
if (!clusterIds.empty())
{
std::ostringstream oss;
oss << "a.id IN (SELECT DISTINCT a.id FROM artist a"
" INNER JOIN track t ON t.id = t_a_l.track_id"
" INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (auto id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(id);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
query.where(oss.str());
}
return query;
}
static
Wt::Dbo::Query<Release::pointer>
createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set<IdType>& clusterIds)
{
auto query {session.query<Release::pointer>(queryStr)};
query.join("track t ON t.release_id = r.id");
query.join("tracklist_entry p_e ON p_e.track_id = t.id");
query.join("tracklist p ON p.id = p_e.tracklist_id");
query.where("p.id = ?").bind(tracklistId);
if (!clusterIds.empty())
{
std::ostringstream oss;
oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
" INNER JOIN track t ON t.release_id = r.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (auto id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(id);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
query.where(oss.str());
}
return query;
}
static
Wt::Dbo::Query<Track::pointer>
createTracksQuery(Wt::Dbo::Session& session, IdType tracklistId, const std::set<IdType>& clusterIds)
{
auto query {session.query<Track::pointer>("SELECT t from track t INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")};
query.where("p.id = ?").bind(tracklistId);
if (!clusterIds.empty())
{
std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause;
for (auto id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
query.bind(id);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")";
query.where(oss.str());
}
return query;
}
std::vector<Artist::pointer>
TrackList::getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Artist::pointer> collection = createArtistsQuery(*session(), "SELECT DISTINCT a from artist a", self()->id(), clusterIds, linkType)
.orderBy("p_e.id DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::size_t
TrackList::getCount() const
std::vector<Release::pointer>
TrackList::getReleasesReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
return _entries.size();
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Release::pointer> collection = createReleasesQuery(*session(), "SELECT DISTINCT r from release r", self()->id(), clusterIds)
.orderBy("p_e.id DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Release::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Track::pointer>
TrackList::getTracksReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Track::pointer> collection = createTracksQuery(*session(), self()->id(), clusterIds)
.orderBy("p_e.id DESC")
.groupBy("t.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Track::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Wt::Dbo::ptr<Cluster>>
@@ -235,48 +411,85 @@ TrackList::getDuration() const
}
std::vector<Artist::pointer>
TrackList::getTopArtists(std::size_t limit) const
TrackList::getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Artist::pointer> res = session()->query<Artist::pointer>("SELECT a from artist a INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")
.where("p.id = ?").bind(self()->id())
.groupBy("a.id")
.orderBy("COUNT(a.id) DESC")
.limit(static_cast<int>(limit));
auto query {createArtistsQuery(*session(), "SELECT a from artist a", self()->id(), clusterIds, linkType)};
return std::vector<Artist::pointer>(res.begin(), res.end());
Wt::Dbo::collection<Artist::pointer> collection = query
.orderBy("COUNT(a.id) DESC")
.groupBy("a.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Release::pointer>
TrackList::getTopReleases(std::size_t limit) const
TrackList::getTopReleases(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Release::pointer> res = session()->query<Release::pointer>("SELECT r from release r INNER JOIN track t ON t.release_id = r.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")
.where("p.id = ?").bind(self()->id())
.groupBy("r.id")
.orderBy("COUNT(r.id) DESC")
.limit(static_cast<int>(limit));
auto query {createReleasesQuery(*session(), "SELECT r from release r", self()->id(), clusterIds)};
return std::vector<Release::pointer>(res.begin(), res.end());
Wt::Dbo::collection<Release::pointer> collection = query
.orderBy("COUNT(r.id) DESC")
.groupBy("r.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Release::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Track::pointer>
TrackList::getTopTracks(std::size_t limit) const
TrackList::getTopTracks(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Track::pointer> res = session()->query<Track::pointer>("SELECT t from track t INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id")
.where("p.id = ?").bind(self()->id())
.groupBy("t.id")
.orderBy("COUNT(t.id) DESC")
.limit(static_cast<int>(limit));
auto query {createTracksQuery(*session(), self()->id(), clusterIds)};
return std::vector<Track::pointer>(res.begin(), res.end());
Wt::Dbo::collection<Track::pointer> collection = query
.orderBy("COUNT(t.id) DESC")
.groupBy("t.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
auto res {std::vector<Track::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
TrackListEntry::TrackListEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<TrackList> tracklist)
+11 -4
View File
@@ -70,14 +70,21 @@ class Artist : public Wt::Dbo::Dbo<Artist>
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords (name + sort name fields)
std::optional<TrackArtistLink::Type> linkType, // if set, only artists that have produced at least one track with this link type
SortMethod sortMethod,
std::optional<std::size_t> offset,
std::optional<std::size_t> size,
std::optional<Range> range,
bool& moreExpected);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod, std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {});
static std::vector<pointer> getAll(Session& session);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod, std::optional<Range> range, bool& moreResults);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLink::Type> linkType, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllOrphans(Session& session); // No track related
static std::vector<pointer> getLastWritten(Session& session, Wt::WDateTime after, std::optional<std::size_t> size = {});
static std::vector<pointer> getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusters,
std::optional<TrackArtistLink::Type> linkType, // if set, only artists that have produced at least one track with this link type
std::optional<Range>,
bool& moreResults);
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
// Accessors
+24 -2
View File
@@ -39,6 +39,24 @@ class Db
std::shared_mutex& getMutex() { return _sharedMutex; }
Wt::Dbo::SqlConnectionPool& getConnectionPool() { return *_connectionPool; }
class ScopedConnection
{
public:
ScopedConnection(Wt::Dbo::SqlConnectionPool& pool);
~ScopedConnection();
ScopedConnection(const ScopedConnection& ) = delete;
ScopedConnection(ScopedConnection&& ) = delete;
ScopedConnection& operator=(const ScopedConnection& ) = delete;
ScopedConnection& operator=(ScopedConnection&& ) = delete;
Wt::Dbo::SqlConnection* operator->() const;
private:
Wt::Dbo::SqlConnectionPool& _connectionPool;
std::unique_ptr<Wt::Dbo::SqlConnection> _connection;
};
class ScopedNoForeignKeys
{
public:
@@ -51,9 +69,13 @@ class Db
_db.executeSql("PRAGMA foreign_keys=ON");
}
private:
Db& _db;
ScopedNoForeignKeys(const ScopedNoForeignKeys&) = delete;
ScopedNoForeignKeys(ScopedNoForeignKeys&&) = delete;
ScopedNoForeignKeys& operator=(const ScopedNoForeignKeys&) = delete;
ScopedNoForeignKeys& operator=(ScopedNoForeignKeys&&) = delete;
private:
Db& _db;
};
void executeSql(const std::string& sql);
@@ -52,19 +52,19 @@ class Release : public Wt::Dbo::Dbo<Release>
static std::vector<pointer> getByName(Session& session, const std::string& name);
static pointer getById(Session& session, IdType id);
static std::vector<pointer> getAllOrphans(Session& session); // no track related
static std::vector<pointer> getAll(Session& session, std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {});
static std::vector<pointer> getAll(Session& session, std::optional<Range> range = std::nullopt);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<pointer> getAllOrderedByArtist(Session& session, std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllRandom(Session& session, std::optional<std::size_t> size = {});
static std::vector<pointer> getLastWritten(Session& session, const Wt::WDateTime& after, std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> size = {});
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> size = {});
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getByYear(Session& session, int yearFrom, int yearTo, std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {});
static std::vector<pointer> getByClusters(Session& session, const std::set<IdType>& clusters);
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, at least one release that belongs to these clusters
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords
std::optional<std::size_t> offset,
std::optional<std::size_t> size,
std::optional<Range> range,
bool& moreExpected);
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
+5 -5
View File
@@ -68,16 +68,16 @@ class Track : public Wt::Dbo::Dbo<Track>
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, tracks that belong to these clusters
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords
std::optional<std::size_t> offset,
std::optional<std::size_t> size,
std::optional<Range> range,
bool& moreExpected);
static std::vector<pointer> getAll(Session& session, std::optional<std::size_t> limit = {});
static std::vector<pointer> getAllRandom(Session& session, std::optional<std::size_t> limit = {});
static std::vector<pointer> getAll(Session& session, std::optional<std::size_t> limit = std::nullopt);
static std::vector<pointer> getAllRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<std::size_t> limit = std::nullopt);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<std::pair<IdType, std::filesystem::path>> getAllPaths(Session& session, std::optional<std::size_t> offset = std::nullopt, std::optional<std::size_t> size = std::nullopt);
static std::vector<pointer> getMBIDDuplicates(Session& session);
static std::vector<pointer> getLastWritten(Session& session, const Wt::WDateTime& after, std::optional<std::size_t> size = 1);
static std::vector<pointer> getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::set<IdType>& clusters, std::optional<Range> range, bool& moreResults);
static std::vector<pointer> getAllWithMBIDAndMissingFeatures(Session& session);
static std::vector<IdType> getAllIdsWithFeatures(Session& session, std::optional<std::size_t> limit = {});
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
@@ -21,9 +21,11 @@
#include <optional>
#include <string>
#include <set>
#include <Wt/Dbo/Dbo.h>
#include "TrackArtistLink.hpp"
#include "Types.hpp"
namespace Database {
@@ -51,9 +53,9 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
TrackList(const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user);
// Stats utility
std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(std::size_t limit = 1) const;
std::vector<Wt::Dbo::ptr<Release>> getTopReleases(std::size_t limit = 1) const;
std::vector<Wt::Dbo::ptr<Track>> getTopTracks(std::size_t limit = 1) const;
std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Release>> getTopReleases(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Track>> getTopTracks(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
// Search utility
static pointer get(Session& session, const std::string& name, Type type, Wt::Dbo::ptr<User> user);
@@ -82,6 +84,10 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntries(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntriesReverse(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
std::vector<Wt::Dbo::ptr<Artist>> getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Release>> getReleasesReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Track>> getTracksReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<IdType> getTrackIds() const;
std::chrono::milliseconds getDuration() const;
+8 -1
View File
@@ -21,12 +21,19 @@
#include <Wt/Dbo/ptr.h>
namespace Database {
namespace Database
{
using IdType = Wt::Dbo::dbo_default_traits::IdType;
static inline bool IdIsValid(IdType id)
{
return id != Wt::Dbo::dbo_default_traits::invalidId();
}
struct Range
{
std::size_t offset {};
std::size_t limit {};
};
}