Replaced UUID storage from str to array of bytes

This commit is contained in:
emeric
2026-06-13 16:54:21 +02:00
parent 309c39a572
commit af018a9166
43 changed files with 392 additions and 140 deletions
+40 -1
View File
@@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
static constexpr Version LMS_DATABASE_VERSION{ 106 };
static constexpr Version LMS_DATABASE_VERSION{ 107 };
}
VersionInfo::VersionInfo()
@@ -1736,6 +1736,44 @@ FROM track)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE "user" ADD COLUMN "lastfm_session_key" TEXT NOT NULL DEFAULT '')");
}
void migrateFromV106(Session& session)
{
dropIndexes(session);
// Convert the 5 MBID TEXT columns to BLOB (16 raw bytes)
// unhex() returns NULL for non-hex input, so malformed values become NULL
// artist.mbid
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE artist ADD COLUMN mbid_new BLOB)");
utils::executeCommand(*session.getDboSession(), R"(UPDATE artist SET mbid_new = CASE WHEN mbid != '' THEN unhex(replace(mbid, '-', '')) ELSE NULL END)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE artist DROP COLUMN mbid)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE artist RENAME COLUMN mbid_new TO mbid)");
// release.mbid
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release ADD COLUMN mbid_new BLOB)");
utils::executeCommand(*session.getDboSession(), R"(UPDATE release SET mbid_new = CASE WHEN mbid != '' THEN unhex(replace(mbid, '-', '')) ELSE NULL END)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release DROP COLUMN mbid)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release RENAME COLUMN mbid_new TO mbid)");
// release.group_mbid
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release ADD COLUMN group_mbid_new BLOB)");
utils::executeCommand(*session.getDboSession(), R"(UPDATE release SET group_mbid_new = CASE WHEN group_mbid != '' THEN unhex(replace(group_mbid, '-', '')) ELSE NULL END)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release DROP COLUMN group_mbid)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release RENAME COLUMN group_mbid_new TO group_mbid)");
// track.mbid
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track ADD COLUMN mbid_new BLOB)");
utils::executeCommand(*session.getDboSession(), R"(UPDATE track SET mbid_new = CASE WHEN mbid != '' THEN unhex(replace(mbid, '-', '')) ELSE NULL END)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track DROP COLUMN mbid)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track RENAME COLUMN mbid_new TO mbid)");
// track.recording_mbid
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track ADD COLUMN recording_mbid_new BLOB)");
utils::executeCommand(*session.getDboSession(), R"(UPDATE track SET recording_mbid_new = CASE WHEN recording_mbid != '' THEN unhex(replace(recording_mbid, '-', '')) ELSE NULL END)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track DROP COLUMN recording_mbid)");
utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track RENAME COLUMN recording_mbid_new TO recording_mbid)");
}
bool doDbMigration(Session& session)
{
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -1818,6 +1856,7 @@ FROM track)");
{ 103, migrateFromV103 },
{ 104, migrateFromV104 },
{ 105, migrateFromV105 },
{ 106, migrateFromV106 },
};
bool migrationPerformed{};
+1
View File
@@ -66,6 +66,7 @@
#include "traits/ImageHashTypeTraits.hpp"
#include "traits/PartialDateTimeTraits.hpp"
#include "traits/PathTraits.hpp"
#include "traits/UUIDTraits.hpp"
namespace lms::db
{
+3 -13
View File
@@ -35,6 +35,7 @@
#include "objects/detail/Types.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/StringViewTraits.hpp"
#include "traits/UUIDTraits.hpp"
DBO_INSTANTIATE_TEMPLATES(lms::db::Artist)
@@ -214,7 +215,7 @@ namespace lms::db
} // namespace
Artist::Artist(const std::string& name, const std::optional<core::UUID>& mbid)
: _mbid{ mbid ? mbid->getAsString() : "" }
: _mbid{ mbid }
{
setName(name);
}
@@ -277,7 +278,7 @@ namespace lms::db
Artist::pointer Artist::find(Session& session, const core::UUID& mbid)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a").where("a.mbid = ?").bind(std::string{ mbid.getAsString() }));
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a").where("a.mbid = ?").bind(mbid));
}
Artist::pointer Artist::find(Session& session, ArtistId id)
@@ -386,17 +387,6 @@ AND NOT EXISTS (
utils::executeCommand(*session.getDboSession(), "UPDATE artist SET preferred_artwork_id = NULL WHERE id = ?", artistId);
}
std::optional<core::UUID> Artist::getMBID() const
{
return core::UUID::fromString(_mbid);
}
bool Artist::hasMBID() const
{
// TODO optim this
return getMBID().has_value();
}
ObjectPtr<Artwork> Artist::getPreferredArtwork() const
{
return ObjectPtr<Artwork>{ _preferredArtwork };
@@ -112,13 +112,13 @@ namespace lms::db
query.where("a_i.mbid_matched = FALSE");
if (!allowArtistMBIDFallback)
{
query.where("a.mbid <> ''");
query.where("a.mbid IS NOT NULL");
}
else
{
query.where(R"(
(a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid))
OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))");
(a.mbid IS NOT NULL AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL AND a2.mbid <> a.mbid))
OR (a.mbid IS NULL AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL) = 1))");
}
utils::applyRange(query, range);
+4 -3
View File
@@ -46,6 +46,7 @@
#include "traits/IdTypeTraits.hpp"
#include "traits/PartialDateTimeTraits.hpp"
#include "traits/StringViewTraits.hpp"
#include "traits/UUIDTraits.hpp"
DBO_INSTANTIATE_TEMPLATES(lms::db::Country)
DBO_INSTANTIATE_TEMPLATES(lms::db::Label)
@@ -218,7 +219,7 @@ namespace lms::db
query.where("t.codec = ?").bind(detail::getDbCodec(params.filters.codec.value()));
if (params.releaseGroupMBID)
query.where("group_mbid = ?").bind(params.releaseGroupMBID->getAsString());
query.where("group_mbid = ?").bind(*params.releaseGroupMBID);
switch (params.sortMethod)
{
@@ -454,7 +455,7 @@ namespace lms::db
Release::Release(const std::string& name, const std::optional<core::UUID>& MBID)
: _name{ std::string(name, 0, _maxNameLength) }
, _MBID{ MBID ? MBID->getAsString() : "" }
, _MBID{ MBID }
{
}
@@ -467,7 +468,7 @@ namespace lms::db
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Release>>("SELECT r from release r").where("r.mbid = ?").bind(mbid.getAsString()));
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Release>>("SELECT r from release r").where("r.mbid = ?").bind(mbid));
}
Release::pointer Release::find(Session& session, ReleaseId id)
@@ -123,13 +123,13 @@ namespace lms::db
query.where("r_a_l.artist_mbid_matched = FALSE");
if (!allowArtistMBIDFallback)
{
query.where("a.mbid <> ''");
query.where("a.mbid IS NOT NULL");
}
else
{
query.where(R"(
(a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid))
OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))");
(a.mbid IS NOT NULL AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL AND a2.mbid <> a.mbid))
OR (a.mbid IS NULL AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL) = 1))");
}
utils::applyRange(query, range);
+4 -3
View File
@@ -46,6 +46,7 @@
#include "traits/PartialDateTimeTraits.hpp"
#include "traits/PathTraits.hpp"
#include "traits/StringViewTraits.hpp"
#include "traits/UUIDTraits.hpp"
DBO_INSTANTIATE_TEMPLATES(lms::db::Track)
@@ -392,21 +393,21 @@ namespace lms::db
{
session.checkReadTransaction();
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.mbid = ?").bind(mbid.getAsString()));
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.mbid = ?").bind(mbid));
}
std::vector<Track::pointer> Track::findByRecordingMBID(Session& session, const core::UUID& mbid)
{
session.checkReadTransaction();
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.recording_mbid = ?").bind(mbid.getAsString()));
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.recording_mbid = ?").bind(mbid));
}
RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<TrackId>("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.mbid") };
auto query{ session.getDboSession()->query<TrackId>("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid IS NOT NULL GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.mbid") };
return utils::execRangeQuery<TrackId>(query, range);
}
@@ -165,13 +165,13 @@ namespace lms::db
query.where("t_a_l.artist_mbid_matched = FALSE");
if (!allowArtistMBIDFallback)
{
query.where("a.mbid <> ''");
query.where("a.mbid IS NOT NULL");
}
else
{
query.where(R"(
(a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid))
OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))");
(a.mbid IS NOT NULL AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL AND a2.mbid <> a.mbid))
OR (a.mbid IS NULL AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL) = 1))");
}
utils::applyRange(query, range);
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2025 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstddef>
#include <cstring>
#include <span>
#include <vector>
#include <Wt/Dbo/StdSqlTraits.h>
#include "core/UUID.hpp"
namespace Wt::Dbo
{
template<>
struct sql_value_traits<lms::core::UUID>
{
static constexpr bool specialized{ true };
using UnderlyingType = std::vector<unsigned char>;
static std::string type(SqlConnection* conn, int size)
{
return sql_value_traits<UnderlyingType, void>::type(conn, size);
}
static void bind(const lms::core::UUID& v, SqlStatement* statement, int column, int size)
{
constexpr auto binarySize{ lms::core::UUID::binarySize };
const auto bytes{ v.bytes() };
UnderlyingType blob(binarySize);
std::memcpy(blob.data(), bytes.data(), binarySize);
sql_value_traits<UnderlyingType>::bind(blob, statement, column, size);
}
static bool read(lms::core::UUID& v, SqlStatement* statement, int column, int size)
{
constexpr auto binarySize{ lms::core::UUID::binarySize };
UnderlyingType buf;
if (!sql_value_traits<UnderlyingType>::read(buf, statement, column, size) || buf.size() != binarySize)
return false;
v = lms::core::UUID::fromBytes(std::span<const std::byte, binarySize>{ reinterpret_cast<const std::byte*>(buf.data()), binarySize });
return true;
}
};
} // namespace Wt::Dbo