diff --git a/src/collection/collectionbackend.cpp b/src/collection/collectionbackend.cpp index 3d5c10c9..3b6c3fa0 100644 --- a/src/collection/collectionbackend.cpp +++ b/src/collection/collectionbackend.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2023, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -401,7 +401,7 @@ SongList CollectionBackend::FindSongsInDirectory(const int id) { QSqlDatabase db(db_->Connect()); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id").arg(Song::kColumnSpec, songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE directory_id = :directory_id").arg(Song::kRowIdColumnSpec, songs_table_)); q.BindValue(QStringLiteral(":directory_id"), id); if (!q.Exec()) { db_->ReportErrors(q); @@ -424,7 +424,7 @@ SongList CollectionBackend::SongsWithMissingFingerprint(const int id) { QSqlDatabase db(db_->Connect()); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (fingerprint IS NULL OR fingerprint = '')").arg(Song::kColumnSpec, songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (fingerprint IS NULL OR fingerprint = '')").arg(Song::kRowIdColumnSpec, songs_table_)); q.BindValue(QStringLiteral(":directory_id"), id); if (!q.Exec()) { db_->ReportErrors(q); @@ -447,7 +447,7 @@ SongList CollectionBackend::SongsWithMissingLoudnessCharacteristics(const int id QSqlDatabase db(db_->Connect()); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (ebur128_integrated_loudness_lufs IS NULL OR ebur128_loudness_range_lu IS NULL)").arg(Song::kColumnSpec, songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (ebur128_integrated_loudness_lufs IS NULL OR ebur128_loudness_range_lu IS NULL)").arg(Song::kRowIdColumnSpec, songs_table_)); q.BindValue(QStringLiteral(":directory_id"), id); if (!q.Exec()) { db_->ReportErrors(q); @@ -548,7 +548,7 @@ SongList CollectionBackend::GetAllSongs() { QSqlDatabase db(db_->Connect()); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2").arg(Song::kColumnSpec, songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2").arg(Song::kRowIdColumnSpec, songs_table_)); if (!q.Exec()) { db_->ReportErrors(q); return SongList(); @@ -1164,7 +1164,7 @@ SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &d QString in = ids.join(QStringLiteral(",")); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE ROWID IN (%3)").arg(Song::kColumnSpec, songs_table_, in)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE ROWID IN (%3)").arg(Song::kRowIdColumnSpec, songs_table_, in)); if (!q.Exec()) { db_->ReportErrors(q); return SongList(); @@ -1186,7 +1186,7 @@ Song CollectionBackend::GetSongByUrl(const QUrl &url, const qint64 beginning) { QSqlDatabase db(db_->Connect()); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND beginning = :beginning AND unavailable = 0").arg(Song::kColumnSpec, songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND beginning = :beginning AND unavailable = 0").arg(Song::kRowIdColumnSpec, songs_table_)); q.BindValue(QStringLiteral(":url1"), url); q.BindValue(QStringLiteral(":url2"), url.toString()); @@ -1216,7 +1216,7 @@ Song CollectionBackend::GetSongByUrlAndTrack(const QUrl &url, const int track) { QSqlDatabase db(db_->Connect()); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND track = :track AND unavailable = 0").arg(Song::kColumnSpec, songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND track = :track AND unavailable = 0").arg(Song::kRowIdColumnSpec, songs_table_)); q.BindValue(QStringLiteral(":url1"), url); q.BindValue(QStringLiteral(":url2"), url.toString()); @@ -1246,7 +1246,7 @@ SongList CollectionBackend::GetSongsByUrl(const QUrl &url, const bool unavailabl QSqlDatabase db(db_->Connect()); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = :unavailable").arg(Song::kColumnSpec, songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = :unavailable").arg(Song::kRowIdColumnSpec, songs_table_)); q.BindValue(QStringLiteral(":url1"), url); q.BindValue(QStringLiteral(":url2"), url.toString()); @@ -1306,7 +1306,7 @@ SongList CollectionBackend::GetSongsBySongId(const QStringList &song_ids, QSqlDa QString in = song_ids2.join(QLatin1Char(',')); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE SONG_ID IN (%3)").arg(Song::kColumnSpec, songs_table_, in)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE SONG_ID IN (%3)").arg(Song::kRowIdColumnSpec, songs_table_, in)); if (!q.Exec()) { db_->ReportErrors(q); return SongList(); @@ -1329,7 +1329,7 @@ SongList CollectionBackend::GetSongsByFingerprint(const QString &fingerprint) { QSqlDatabase db(db_->Connect()); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE fingerprint = :fingerprint").arg(Song::kColumnSpec, songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE fingerprint = :fingerprint").arg(Song::kRowIdColumnSpec, songs_table_)); q.BindValue(QStringLiteral(":fingerprint"), fingerprint); if (!q.Exec()) { db_->ReportErrors(q); @@ -1452,7 +1452,7 @@ bool CollectionBackend::UpdateCompilations(const QSqlDatabase &db, SongList &del { // Get song, so we can tell the model its updated SqlQuery q(db); - q.prepare(QStringLiteral("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0").arg(Song::kColumnSpec, songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0").arg(Song::kRowIdColumnSpec, songs_table_)); q.BindValue(QStringLiteral(":url1"), url); q.BindValue(QStringLiteral(":url2"), url.toString()); q.BindValue(QStringLiteral(":url3"), url.toString(QUrl::FullyEncoded)); @@ -1622,7 +1622,7 @@ void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumart // Get the songs before they're updated CollectionQuery query(db, songs_table_, fts_table_); - query.SetColumnSpec(QStringLiteral("ROWID, ") + Song::kColumnSpec); + query.SetColumnSpec(Song::kRowIdColumnSpec); query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist); query.AddWhere(QStringLiteral("album"), album); @@ -1684,7 +1684,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis QSqlDatabase db(db_->Connect()); CollectionQuery query(db, songs_table_, fts_table_); - query.SetColumnSpec(QStringLiteral("ROWID, ") + Song::kColumnSpec); + query.SetColumnSpec(Song::kRowIdColumnSpec); query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist); query.AddWhere(QStringLiteral("album"), album); @@ -1742,7 +1742,7 @@ void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, cons QSqlDatabase db(db_->Connect()); CollectionQuery query(db, songs_table_, fts_table_); - query.SetColumnSpec(QStringLiteral("ROWID, ") + Song::kColumnSpec); + query.SetColumnSpec(Song::kRowIdColumnSpec); query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist); query.AddWhere(QStringLiteral("album"), album); @@ -1799,7 +1799,7 @@ void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, cons QSqlDatabase db(db_->Connect()); CollectionQuery query(db, songs_table_, fts_table_); - query.SetColumnSpec(QStringLiteral("ROWID, ") + Song::kColumnSpec); + query.SetColumnSpec(Song::kRowIdColumnSpec); query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist); query.AddWhere(QStringLiteral("album"), album); @@ -1854,7 +1854,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QListMutex()); QSqlDatabase db(db_->Connect()); SqlQuery q(db); - q.prepare(QStringLiteral("SELECT %1.ROWID, ").arg(songs_table_) + Song::JoinSpec(songs_table_) + QStringLiteral(" FROM %1 LEFT JOIN playlist_items ON %1.ROWID = playlist_items.collection_id WHERE %1.directory_id = :directory_id AND %1.unavailable = 1 AND %1.lastseen > 0 AND %1.lastseen < :time AND playlist_items.collection_id IS NULL").arg(songs_table_)); + q.prepare(QStringLiteral("SELECT %1 FROM %2 LEFT JOIN playlist_items ON %2.ROWID = playlist_items.collection_id WHERE %2.directory_id = :directory_id AND %2.unavailable = 1 AND %2.lastseen > 0 AND %2.lastseen < :time AND playlist_items.collection_id IS NULL").arg(Song::JoinSpec(songs_table_), songs_table_)); q.BindValue(QStringLiteral(":directory_id"), directory_id); q.BindValue(QStringLiteral(":time"), QDateTime::currentDateTime().toSecsSinceEpoch() - (expire_unavailable_songs_days * 86400)); if (!q.Exec()) { diff --git a/src/collection/collectionbackend.h b/src/collection/collectionbackend.h index 72f9aab5..b19d24f0 100644 --- a/src/collection/collectionbackend.h +++ b/src/collection/collectionbackend.h @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2023, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/collection/collectionquery.cpp b/src/collection/collectionquery.cpp index e8e8c482..bee96b8b 100644 --- a/src/collection/collectionquery.cpp +++ b/src/collection/collectionquery.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2021, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,8 +30,8 @@ #include #include #include -#include +#include "core/sqlquery.h" #include "core/song.h" #include "collectionquery.h" @@ -39,7 +39,7 @@ #include "utilities/searchparserutils.h" CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options) - : QSqlQuery(db), + : SqlQuery(db), songs_table_(songs_table), fts_table_(fts_table), include_unavailable_(false), diff --git a/src/collection/collectionquery.h b/src/collection/collectionquery.h index c8952706..ce705303 100644 --- a/src/collection/collectionquery.h +++ b/src/collection/collectionquery.h @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2021, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,11 +29,12 @@ #include #include #include -#include + +#include "core/sqlquery.h" #include "collectionfilteroptions.h" -class CollectionQuery : public QSqlQuery { +class CollectionQuery : public SqlQuery { public: explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions()); @@ -41,7 +42,7 @@ class CollectionQuery : public QSqlQuery { QVariant value(const int column) const { return Value(column); } bool Exec(); - bool exec() { return QSqlQuery::exec(); } + bool exec() { return SqlQuery::exec(); } bool Next(); diff --git a/src/core/song.cpp b/src/core/song.cpp index 4909e316..bbb91548 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2023, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,6 +46,7 @@ #include #include #include +#include #include "core/iconloader.h" #include "engine/enginemetadata.h" @@ -53,6 +54,7 @@ #include "utilities/timeutils.h" #include "utilities/coverutils.h" #include "utilities/timeconstants.h" +#include "utilities/sqlhelper.h" #include "song.h" #include "sqlquery.h" #include "sqlrow.h" @@ -140,7 +142,10 @@ const QStringList Song::kColumns = QStringList() << QStringLiteral("title") ; +const QStringList Song::kRowIdColumns = QStringList() << QStringLiteral("ROWID") << kColumns; + const QString Song::kColumnSpec = Song::kColumns.join(QStringLiteral(", ")); +const QString Song::kRowIdColumnSpec = Song::kRowIdColumns.join(QStringLiteral(", ")); const QString Song::kBindSpec = Utilities::Prepend(QStringLiteral(":"), Song::kColumns).join(QStringLiteral(", ")); const QString Song::kUpdateSpec = Utilities::Updateify(Song::kColumns).join(QStringLiteral(", ")); @@ -668,7 +673,7 @@ QString Song::sortable(const QString &v) { } QString Song::JoinSpec(const QString &table) { - return Utilities::Prepend(table + QLatin1Char('.'), kColumns).join(QStringLiteral(", ")); + return Utilities::Prepend(table + QLatin1Char('.'), kRowIdColumns).join(QStringLiteral(", ")); } QString Song::PrettyTitle() const { @@ -1364,78 +1369,80 @@ void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const { } -void Song::InitFromQuery(const SqlRow &q, const bool reliable_metadata) { +void Song::InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col) { - d->id_ = q.value(QStringLiteral("rowid")).isNull() ? -1 : q.value(QStringLiteral("rowid")).toInt(); + Q_ASSERT(kRowIdColumns.count() + col <= r.count()); - set_title(q.ValueToString(QStringLiteral("title"))); - set_album(q.ValueToString(QStringLiteral("album"))); - set_artist(q.ValueToString(QStringLiteral("artist"))); - set_albumartist(q.ValueToString(QStringLiteral("albumartist"))); - d->track_ = q.ValueToInt(QStringLiteral("track")); - d->disc_ = q.ValueToInt(QStringLiteral("disc")); - d->year_ = q.ValueToInt(QStringLiteral("year")); - d->originalyear_ = q.ValueToInt(QStringLiteral("originalyear")); - d->genre_ = q.ValueToString(QStringLiteral("genre")); - d->compilation_ = q.value(QStringLiteral("compilation")).toBool(); - d->composer_ = q.ValueToString(QStringLiteral("composer")); - d->performer_ = q.ValueToString(QStringLiteral("performer")); - d->grouping_ = q.ValueToString(QStringLiteral("grouping")); - d->comment_ = q.ValueToString(QStringLiteral("comment")); - d->lyrics_ = q.ValueToString(QStringLiteral("lyrics")); - d->artist_id_ = q.ValueToString(QStringLiteral("artist_id")); - d->album_id_ = q.ValueToString(QStringLiteral("album_id")); - d->song_id_ = q.ValueToString(QStringLiteral("song_id")); - d->beginning_ = q.value(QStringLiteral("beginning")).isNull() ? 0 : q.value(QStringLiteral("beginning")).toLongLong(); - set_length_nanosec(q.ValueToLongLong(QStringLiteral("length"))); - d->bitrate_ = q.ValueToInt(QStringLiteral("bitrate")); - d->samplerate_ = q.ValueToInt(QStringLiteral("samplerate")); - d->bitdepth_ = q.ValueToInt(QStringLiteral("bitdepth")); - if (!q.value(QStringLiteral("ebur128_integrated_loudness_lufs")).isNull()) { - d->ebur128_integrated_loudness_lufs_ = q.value(QStringLiteral("ebur128_integrated_loudness_lufs")).toDouble(); + d->id_ = SqlHelper::ValueToInt(r, kRowIdColumns.indexOf(QStringLiteral("ROWID")) + col); + + set_title(SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("title")) + col)); + set_album(SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("album")) + col)); + set_artist(SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("artist")) + col)); + set_albumartist(SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("albumartist")) + col)); + d->track_ = SqlHelper::ValueToInt(r, kRowIdColumns.indexOf(QStringLiteral("track")) + col); + d->disc_ = SqlHelper::ValueToInt(r, kRowIdColumns.indexOf(QStringLiteral("disc")) + col); + d->year_ = SqlHelper::ValueToInt(r, kRowIdColumns.indexOf(QStringLiteral("year")) + col); + d->originalyear_ = SqlHelper::ValueToInt(r, kRowIdColumns.indexOf(QStringLiteral("originalyear")) + col); + d->genre_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("genre")) + col); + d->compilation_ = r.value(kRowIdColumns.indexOf(QStringLiteral("compilation")) + col).toBool(); + d->composer_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("composer")) + col); + d->performer_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("performer")) + col); + d->grouping_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("grouping")) + col); + d->comment_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("comment")) + col); + d->lyrics_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("lyrics")) + col); + d->artist_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("artist_id")) + col); + d->album_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("album_id")) + col); + d->song_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("song_id")) + col); + d->beginning_ = r.value(kRowIdColumns.indexOf(QStringLiteral("beginning")) + col).isNull() ? 0 : r.value(kRowIdColumns.indexOf(QStringLiteral("beginning")) + col).toLongLong(); + set_length_nanosec(SqlHelper::ValueToLongLong(r, kRowIdColumns.indexOf(QStringLiteral("length"))) + col); + d->bitrate_ = SqlHelper::ValueToInt(r, kRowIdColumns.indexOf(QStringLiteral("bitrate")) + col); + d->samplerate_ = SqlHelper::ValueToInt(r, kRowIdColumns.indexOf(QStringLiteral("samplerate")) + col); + d->bitdepth_ = SqlHelper::ValueToInt(r, kRowIdColumns.indexOf(QStringLiteral("bitdepth")) + col); + if (!r.value(kRowIdColumns.indexOf(QStringLiteral("ebur128_integrated_loudness_lufs")) + col).isNull()) { + d->ebur128_integrated_loudness_lufs_ = r.value(kRowIdColumns.indexOf(QStringLiteral("ebur128_integrated_loudness_lufs")) + col).toDouble(); } - if (!q.value(QStringLiteral("ebur128_loudness_range_lu")).isNull()) { - d->ebur128_loudness_range_lu_ = q.value(QStringLiteral("ebur128_loudness_range_lu")).toDouble(); + if (!r.value(kRowIdColumns.indexOf(QStringLiteral("ebur128_loudness_range_lu")) + col).isNull()) { + d->ebur128_loudness_range_lu_ = r.value(kRowIdColumns.indexOf(QStringLiteral("ebur128_loudness_range_lu")) + col).toDouble(); } - d->source_ = static_cast(q.value(QStringLiteral("source")).isNull() ? 0 : q.value(QStringLiteral("source")).toInt()); - d->directory_id_ = q.ValueToInt(QStringLiteral("directory_id")); - set_url(QUrl::fromEncoded(q.ValueToString(QStringLiteral("url")).toUtf8())); + d->source_ = static_cast(r.value(kRowIdColumns.indexOf(QStringLiteral("source")) + col).isNull() ? 0 : r.value(kRowIdColumns.indexOf(QStringLiteral("source")) + col).toInt()); + d->directory_id_ = SqlHelper::ValueToInt(r, kRowIdColumns.indexOf(QStringLiteral("directory_id")) + col); + set_url(QUrl::fromEncoded(SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("url")) + col).toUtf8())); d->basefilename_ = QFileInfo(d->url_.toLocalFile()).fileName(); - d->filetype_ = FileType(q.value(QStringLiteral("filetype")).isNull() ? 0 : q.value(QStringLiteral("filetype")).toInt()); - d->filesize_ = q.ValueToLongLong(QStringLiteral("filesize")); - d->mtime_ = q.ValueToLongLong(QStringLiteral("mtime")); - d->ctime_ = q.ValueToLongLong(QStringLiteral("ctime")); - d->unavailable_ = q.value(QStringLiteral("unavailable")).toBool(); - d->fingerprint_ = q.ValueToString(QStringLiteral("fingerprint")); - d->playcount_ = q.ValueToUInt(QStringLiteral("playcount")); - d->skipcount_ = q.ValueToUInt(QStringLiteral("skipcount")); - d->lastplayed_ = q.ValueToLongLong(QStringLiteral("lastplayed")); - d->lastseen_ = q.ValueToLongLong(QStringLiteral("lastseen")); - d->compilation_detected_ = q.ValueToBool(QStringLiteral("compilation_detected")); - d->compilation_on_ = q.ValueToBool(QStringLiteral("compilation_on")); - d->compilation_off_ = q.ValueToBool(QStringLiteral("compilation_off")); + d->filetype_ = FileType(r.value(kRowIdColumns.indexOf(QStringLiteral("filetype")) + col).isNull() ? 0 : r.value(kRowIdColumns.indexOf(QStringLiteral("filetype")) + col).toInt()); + d->filesize_ = SqlHelper::ValueToLongLong(r, kRowIdColumns.indexOf(QStringLiteral("filesize")) + col); + d->mtime_ = SqlHelper::ValueToLongLong(r, kRowIdColumns.indexOf(QStringLiteral("mtime")) + col); + d->ctime_ = SqlHelper::ValueToLongLong(r, kRowIdColumns.indexOf(QStringLiteral("ctime")) + col); + d->unavailable_ = r.value(kRowIdColumns.indexOf(QStringLiteral("unavailable")) + col).toBool(); + d->fingerprint_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("fingerprint")) + col); + d->playcount_ = SqlHelper::ValueToUInt(r, kRowIdColumns.indexOf(QStringLiteral("playcount")) + col); + d->skipcount_ = SqlHelper::ValueToUInt(r, kRowIdColumns.indexOf(QStringLiteral("skipcount")) + col); + d->lastplayed_ = SqlHelper::ValueToLongLong(r, kRowIdColumns.indexOf(QStringLiteral("lastplayed")) + col); + d->lastseen_ = SqlHelper::ValueToLongLong(r, kRowIdColumns.indexOf(QStringLiteral("lastseen")) + col); + d->compilation_detected_ = SqlHelper::ValueToBool(r, kRowIdColumns.indexOf(QStringLiteral("compilation_detected")) + col); + d->compilation_on_ = SqlHelper::ValueToBool(r, kRowIdColumns.indexOf(QStringLiteral("compilation_on")) + col); + d->compilation_off_ = SqlHelper::ValueToBool(r, kRowIdColumns.indexOf(QStringLiteral("compilation_off")) + col); - d->art_embedded_ = q.ValueToBool(QStringLiteral("art_embedded")); - d->art_automatic_ = QUrl::fromEncoded(q.ValueToString(QStringLiteral("art_automatic")).toUtf8()); - d->art_manual_ = QUrl::fromEncoded(q.ValueToString(QStringLiteral("art_manual")).toUtf8()); - d->art_unset_ = q.ValueToBool(QStringLiteral("art_unset")); + d->art_embedded_ = SqlHelper::ValueToBool(r, kRowIdColumns.indexOf(QStringLiteral("art_embedded")) + col); + d->art_automatic_ = QUrl::fromEncoded(SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("art_automatic")) + col).toUtf8()); + d->art_manual_ = QUrl::fromEncoded(SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("art_manual")) + col).toUtf8()); + d->art_unset_ = SqlHelper::ValueToBool(r, kRowIdColumns.indexOf(QStringLiteral("art_unset")) + col); - d->cue_path_ = q.ValueToString(QStringLiteral("cue_path")); - d->rating_ = q.ValueToFloat(QStringLiteral("rating")); + d->cue_path_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("cue_path")) + col); + d->rating_ = SqlHelper::ValueToFloat(r, kRowIdColumns.indexOf(QStringLiteral("rating")) + col); - d->acoustid_id_ = q.ValueToString(QStringLiteral("acoustid_id")); - d->acoustid_fingerprint_ = q.ValueToString(QStringLiteral("acoustid_fingerprint")); + d->acoustid_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("acoustid_id")) + col); + d->acoustid_fingerprint_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("acoustid_fingerprint")) + col); - d->musicbrainz_album_artist_id_ = q.ValueToString(QStringLiteral("musicbrainz_album_artist_id")); - d->musicbrainz_artist_id_ = q.ValueToString(QStringLiteral("musicbrainz_artist_id")); - d->musicbrainz_original_artist_id_ = q.ValueToString(QStringLiteral("musicbrainz_original_artist_id")); - d->musicbrainz_album_id_ = q.ValueToString(QStringLiteral("musicbrainz_album_id")); - d->musicbrainz_original_album_id_ = q.ValueToString(QStringLiteral("musicbrainz_original_album_id")); - d->musicbrainz_recording_id_ = q.ValueToString(QStringLiteral("musicbrainz_recording_id")); - d->musicbrainz_track_id_ = q.ValueToString(QStringLiteral("musicbrainz_track_id")); - d->musicbrainz_disc_id_ = q.ValueToString(QStringLiteral("musicbrainz_disc_id")); - d->musicbrainz_release_group_id_ = q.ValueToString(QStringLiteral("musicbrainz_release_group_id")); - d->musicbrainz_work_id_ = q.ValueToString(QStringLiteral("musicbrainz_work_id")); + d->musicbrainz_album_artist_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_album_artist_id")) + col); + d->musicbrainz_artist_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_artist_id")) + col); + d->musicbrainz_original_artist_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_original_artist_id")) + col); + d->musicbrainz_album_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_album_id")) + col); + d->musicbrainz_original_album_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_original_album_id")) + col); + d->musicbrainz_recording_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_recording_id")) + col); + d->musicbrainz_track_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_track_id")) + col); + d->musicbrainz_disc_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_disc_id")) + col); + d->musicbrainz_release_group_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_release_group_id")) + col); + d->musicbrainz_work_id_ = SqlHelper::ValueToString(r, kRowIdColumns.indexOf(QStringLiteral("musicbrainz_work_id")) + col); d->valid_ = true; d->init_from_file_ = reliable_metadata; @@ -1444,6 +1451,18 @@ void Song::InitFromQuery(const SqlRow &q, const bool reliable_metadata) { } +void Song::InitFromQuery(const SqlQuery &query, const bool reliable_metadata, const int col) { + + InitFromQuery(query.record(), reliable_metadata, col); + +} + +void Song::InitFromQuery(const SqlRow &row, const bool reliable_metadata, const int col) { + + InitFromQuery(row.record(), reliable_metadata, col); + +} + void Song::InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo) { set_url(QUrl::fromLocalFile(filename)); diff --git a/src/core/song.h b/src/core/song.h index fde0356e..ed230cb9 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2023, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,6 +42,7 @@ #include class SqlQuery; +class QSqlRecord; class EngineMetadata; @@ -112,7 +113,9 @@ class Song { }; static const QStringList kColumns; + static const QStringList kRowIdColumns; static const QString kColumnSpec; + static const QString kRowIdColumnSpec; static const QString kBindSpec; static const QString kUpdateSpec; @@ -416,7 +419,9 @@ class Song { void Init(const QString &title, const QString &artist, const QString &album, const qint64 length_nanosec); void Init(const QString &title, const QString &artist, const QString &album, const qint64 beginning, const qint64 end); void InitFromProtobuf(const spb::tagreader::SongMetadata &pb); - void InitFromQuery(const SqlRow &query, const bool reliable_metadata); + void InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col = 0); + void InitFromQuery(const SqlQuery &query, const bool reliable_metadata, const int col = 0); + void InitFromQuery(const SqlRow &row, const bool reliable_metadata, const int col = 0); void InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo); void InitArtManual(); void InitArtAutomatic(); diff --git a/src/core/sqlquery.cpp b/src/core/sqlquery.cpp index e19d77d7..4fa5f5b9 100644 --- a/src/core/sqlquery.cpp +++ b/src/core/sqlquery.cpp @@ -1,6 +1,6 @@ /* * Strawberry Music Player - * Copyright 2021, Jonas Kvinge + * Copyright 2021-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/sqlquery.h b/src/core/sqlquery.h index 3e9ac116..f9b36e6d 100644 --- a/src/core/sqlquery.h +++ b/src/core/sqlquery.h @@ -1,6 +1,6 @@ /* * Strawberry Music Player - * Copyright 2021, Jonas Kvinge + * Copyright 2021-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,12 +29,15 @@ #include #include #include +#include class SqlQuery : public QSqlQuery { public: explicit SqlQuery(const QSqlDatabase &db) : QSqlQuery(db) {} + int columns() const { return QSqlQuery::record().count(); } + void BindValue(const QString &placeholder, const QVariant &value); void BindStringValue(const QString &placeholder, const QString &value); void BindUrlValue(const QString &placeholder, const QUrl &value); diff --git a/src/core/sqlrow.cpp b/src/core/sqlrow.cpp index de44579a..06a24189 100644 --- a/src/core/sqlrow.cpp +++ b/src/core/sqlrow.cpp @@ -1,6 +1,6 @@ /* * Strawberry Music Player - * Copyright 2018-2021, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,74 +20,22 @@ #include "config.h" #include -#include -#include -#include #include #include "sqlrow.h" -SqlRow::SqlRow(const QSqlQuery &query) { Init(query); } +SqlRow::SqlRow(const SqlQuery &query) { Init(query); } -void SqlRow::Init(const QSqlQuery &query) { +void SqlRow::Init(const SqlQuery &query) { - const QSqlRecord r = query.record(); - for (int i = 0; i < r.count(); ++i) { - columns_by_number_.insert(i, query.value(i)); - const QString field_name = r.fieldName(i); - if (!columns_by_name_.contains(field_name) || columns_by_name_[field_name].isNull()) { - columns_by_name_.insert(field_name, query.value(i)); - } - } + record_ = query.record(); } -const QVariant SqlRow::value(const int number) const { +const QVariant SqlRow::value(const int n) const { - if (columns_by_number_.contains(number)) { - return columns_by_number_[number]; - } - else { - return QVariant(); - } + Q_ASSERT(n < record_.count()); + + return record_.value(n); } - -const QVariant SqlRow::value(const QString &name) const { - - if (columns_by_name_.contains(name)) { - return columns_by_name_[name]; - } - else { - return QVariant(); - } - -} - -QString SqlRow::ValueToString(const QString &n) const { - return value(n).isNull() ? QString() : value(n).toString(); -} - -QUrl SqlRow::ValueToUrl(const QString &n) const { - return value(n).isNull() ? QUrl() : QUrl(value(n).toString()); -} - -int SqlRow::ValueToInt(const QString &n) const { - return value(n).isNull() ? -1 : value(n).toInt(); -} - -uint SqlRow::ValueToUInt(const QString &n) const { - return value(n).isNull() || value(n).toInt() < 0 ? 0 : value(n).toInt(); -} - -qint64 SqlRow::ValueToLongLong(const QString &n) const { - return value(n).isNull() ? -1 : value(n).toLongLong(); -} - -float SqlRow::ValueToFloat(const QString &n) const { - return value(n).isNull() ? -1.0F : value(n).toFloat(); -} - -bool SqlRow::ValueToBool(const QString &n) const { - return !value(n).isNull() && value(n).toInt() == 1; -} diff --git a/src/core/sqlrow.h b/src/core/sqlrow.h index 6db2f9e9..7666e459 100644 --- a/src/core/sqlrow.h +++ b/src/core/sqlrow.h @@ -1,6 +1,6 @@ /* * Strawberry Music Player - * Copyright 2018-2021, Jonas Kvinge + * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,33 +24,23 @@ #include #include -#include -#include +#include + +#include "sqlquery.h" class SqlRow { public: - SqlRow(const QSqlQuery &query); + explicit SqlRow(const SqlQuery &query); - const QVariant value(const int number) const; - const QVariant value(const QString &name) const; - - QString ValueToString(const QString &n) const; - QUrl ValueToUrl(const QString &n) const; - int ValueToInt(const QString &n) const; - uint ValueToUInt(const QString &n) const; - qint64 ValueToLongLong(const QString &n) const; - float ValueToFloat(const QString &n) const; - bool ValueToBool(const QString& n) const; + int columns() const { return record_.count(); } + const QSqlRecord &record() const { return record_; } + const QVariant value(const int n) const; private: - SqlRow(); - - void Init(const QSqlQuery &query); - - QMap columns_by_number_; - QMap columns_by_name_; + void Init(const SqlQuery &query); + QSqlRecord record_; }; using SqlRowList = QList; diff --git a/src/covermanager/albumcovermanager.cpp b/src/covermanager/albumcovermanager.cpp index 641a7d43..6a336fc5 100644 --- a/src/covermanager/albumcovermanager.cpp +++ b/src/covermanager/albumcovermanager.cpp @@ -877,7 +877,7 @@ SongList AlbumCoverManager::GetSongsInAlbum(const QModelIndex &idx) const { QSqlDatabase db(collection_backend_->db()->Connect()); CollectionQuery q(db, collection_backend_->songs_table(), collection_backend_->fts_table()); - q.SetColumnSpec(QStringLiteral("ROWID,") + Song::kColumnSpec); + q.SetColumnSpec(Song::kRowIdColumnSpec); q.AddWhere(QStringLiteral("album"), idx.data(Role_Album).toString()); q.SetOrderBy(QStringLiteral("disc, track, title")); diff --git a/src/internet/internetplaylistitem.cpp b/src/internet/internetplaylistitem.cpp index bd70a374..3b1f9a2a 100644 --- a/src/internet/internetplaylistitem.cpp +++ b/src/internet/internetplaylistitem.cpp @@ -48,7 +48,7 @@ InternetPlaylistItem::InternetPlaylistItem(InternetServicePtr service, const Son bool InternetPlaylistItem::InitFromQuery(const SqlRow &query) { - metadata_.InitFromQuery(query, false); + metadata_.InitFromQuery(query, false, Song::kRowIdColumns.count() * 3); InitMetadata(); return true; diff --git a/src/playlist/playlistbackend.cpp b/src/playlist/playlistbackend.cpp index 662fe3c1..9048b2c6 100644 --- a/src/playlist/playlistbackend.cpp +++ b/src/playlist/playlistbackend.cpp @@ -184,7 +184,8 @@ PlaylistItemPtrList PlaylistBackend::GetPlaylistItems(const int playlist) { QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - QString query = QStringLiteral("SELECT songs.ROWID, ") + Song::JoinSpec(QStringLiteral("songs")) + QStringLiteral(", p.ROWID, ") + Song::JoinSpec(QStringLiteral("p")) + QStringLiteral(", p.type FROM playlist_items AS p LEFT JOIN songs ON p.collection_id = songs.ROWID WHERE p.playlist = :playlist"); + QString query = QStringLiteral("SELECT %1, %2, p.type FROM playlist_items AS p LEFT JOIN songs ON p.collection_id = songs.ROWID WHERE p.playlist = :playlist").arg(Song::JoinSpec(QStringLiteral("songs")), Song::JoinSpec(QStringLiteral("p"))); + SqlQuery q(db); // Forward iterations only may be faster q.setForwardOnly(true); @@ -219,7 +220,8 @@ SongList PlaylistBackend::GetPlaylistSongs(const int playlist) { QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - QString query = QStringLiteral("SELECT songs.ROWID, ") + Song::JoinSpec(QStringLiteral("songs")) + QStringLiteral(", p.ROWID, ") + Song::JoinSpec(QStringLiteral("p")) + QStringLiteral(", p.type FROM playlist_items AS p LEFT JOIN songs ON p.collection_id = songs.ROWID WHERE p.playlist = :playlist"); + QString query = QStringLiteral("SELECT %1, %2, p.type FROM playlist_items AS p LEFT JOIN songs ON p.collection_id = songs.ROWID WHERE p.playlist = :playlist").arg(Song::JoinSpec(QStringLiteral("songs")), Song::JoinSpec(QStringLiteral("p"))); + SqlQuery q(db); // Forward iterations only may be faster q.setForwardOnly(true); @@ -249,7 +251,7 @@ SongList PlaylistBackend::GetPlaylistSongs(const int playlist) { PlaylistItemPtr PlaylistBackend::NewPlaylistItemFromQuery(const SqlRow &row, SharedPtr state) { // The song tables get joined first, plus one each for the song ROWIDs - const int playlist_row = static_cast(Song::kColumns.count() + 1) * kSongTableJoins; + const int playlist_row = static_cast(Song::kRowIdColumns.count()) * kSongTableJoins; PlaylistItemPtr item(PlaylistItem::NewFromSource(static_cast(row.value(playlist_row).toInt()))); if (item) { diff --git a/src/playlist/songplaylistitem.cpp b/src/playlist/songplaylistitem.cpp index 43194912..2d6d38b6 100644 --- a/src/playlist/songplaylistitem.cpp +++ b/src/playlist/songplaylistitem.cpp @@ -33,7 +33,7 @@ SongPlaylistItem::SongPlaylistItem(const Song::Source source) : PlaylistItem(sou SongPlaylistItem::SongPlaylistItem(const Song &song) : PlaylistItem(song.source()), song_(song) {} bool SongPlaylistItem::InitFromQuery(const SqlRow &query) { - song_.InitFromQuery(query, false); + song_.InitFromQuery(query, false, Song::kRowIdColumns.count() * 3); return true; } diff --git a/src/smartplaylists/smartplaylistsearch.cpp b/src/smartplaylists/smartplaylistsearch.cpp index fe6bf14a..9291ceab 100644 --- a/src/smartplaylists/smartplaylistsearch.cpp +++ b/src/smartplaylists/smartplaylistsearch.cpp @@ -51,7 +51,7 @@ void SmartPlaylistSearch::Reset() { QString SmartPlaylistSearch::ToSql(const QString &songs_table) const { - QString sql = QStringLiteral("SELECT ROWID,") + Song::kColumnSpec + QStringLiteral(" FROM ") + songs_table; + QString sql = QStringLiteral("SELECT %1 FROM %2").arg(Song::kRowIdColumnSpec, songs_table); // Add search terms QStringList where_clauses; diff --git a/src/utilities/sqlhelper.h b/src/utilities/sqlhelper.h new file mode 100644 index 00000000..14b27330 --- /dev/null +++ b/src/utilities/sqlhelper.h @@ -0,0 +1,114 @@ +/* + * Strawberry Music Player + * Copyright 2024, Jonas Kvinge + * + * Strawberry 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. + * + * Strawberry 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 Strawberry. If not, see . + * + */ + +#ifndef SQLHELPER_H +#define SQLHELPER_H + +#include +#include +#include + +class SqlHelper { + public: + template + static QString ValueToString(const T &q, const int n); + + template + static QUrl ValueToUrl(const T &q, const int n); + + template + static int ValueToInt(const T &q, const int n); + + template + static uint ValueToUInt(const T &q, const int n); + + template + static qint64 ValueToLongLong(const T &q, const int n); + + template + static float ValueToFloat(const T &q, const int n); + + template + static bool ValueToBool(const T &q, const int n); +}; + +template +QString SqlHelper::ValueToString(const T &q, const int n) { + + Q_ASSERT(n < q.count()); + + return q.value(n).isNull() ? QString() : q.value(n).toString(); + +} + +template +QUrl SqlHelper::ValueToUrl(const T &q, const int n) { + + Q_ASSERT(n < q.count()); + + return q.value(n).isNull() ? QUrl() : QUrl(q.value(n).toString()); + +} + +template +int SqlHelper::ValueToInt(const T &q, const int n) { + + Q_ASSERT(n < q.count()); + + return q.value(n).isNull() ? -1 : q.value(n).toInt(); + +} + +template +uint SqlHelper::ValueToUInt(const T &q, const int n) { + + Q_ASSERT(n < q.count()); + + return q.value(n).isNull() || q.value(n).toInt() < 0 ? 0 : q.value(n).toInt(); + +} + +template +qint64 SqlHelper::ValueToLongLong(const T &q, const int n) { + + Q_ASSERT(n < q.count()); + + return q.value(n).isNull() ? -1 : q.value(n).toLongLong(); + +} + +template +float SqlHelper::ValueToFloat(const T &q, const int n) { + + Q_ASSERT(n < q.count()); + + return q.value(n).isNull() ? -1.0F : q.value(n).toFloat(); + +} + +template +bool SqlHelper::ValueToBool(const T &q, const int n) { + + Q_ASSERT(n < q.count()); + + return !q.value(n).isNull() && q.value(n).toInt() == 1; + +} + +#endif // SQLHELPER_H