|
|
|
@@ -0,0 +1,324 @@
|
|
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2021 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/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "Common.hpp"
|
|
|
|
|
#include "core/String.hpp"
|
|
|
|
|
#include "database/Db.hpp"
|
|
|
|
|
|
|
|
|
|
namespace lms::db::tests
|
|
|
|
|
{
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
void executeStatements(db::Session& session, std::string_view statements)
|
|
|
|
|
{
|
|
|
|
|
auto transaction{ session.createWriteTransaction() };
|
|
|
|
|
|
|
|
|
|
for (std::string_view statement : core::stringUtils::splitString(statements, ';'))
|
|
|
|
|
{
|
|
|
|
|
statement = core::stringUtils::stringTrim(statement, " \t\r\n");
|
|
|
|
|
if (!statement.empty())
|
|
|
|
|
session.execute(statement);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(Database, migration)
|
|
|
|
|
{
|
|
|
|
|
TmpDatabase tmpDatabase;
|
|
|
|
|
|
|
|
|
|
auto& db{ tmpDatabase.getDb() };
|
|
|
|
|
|
|
|
|
|
// Create tables and indexes as they were in version 33:
|
|
|
|
|
const std::string_view schemaV33{ R"(
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "cluster_type" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"name" text not null,
|
|
|
|
|
"scan_settings_id" bigint,
|
|
|
|
|
constraint "fk_cluster_type_scan_settings" foreign key ("scan_settings_id") references "scan_settings" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "version_info" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"db_version" integer not null
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "scan_settings" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"scan_version" integer not null,
|
|
|
|
|
"media_directory" text not null,
|
|
|
|
|
"start_time" integer,
|
|
|
|
|
"update_period" integer not null,
|
|
|
|
|
"audio_file_extensions" text not null,
|
|
|
|
|
"similarity_engine_type" integer not null
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "track_features" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"data" text not null,
|
|
|
|
|
"track_id" bigint,
|
|
|
|
|
constraint "fk_track_features_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "tracklist_entry" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"track_id" bigint,
|
|
|
|
|
"tracklist_id" bigint, date_time TEXT,
|
|
|
|
|
constraint "fk_tracklist_entry_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
|
|
|
|
|
constraint "fk_tracklist_entry_tracklist" foreign key ("tracklist_id") references "tracklist" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "track_artist_link" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"type" integer not null,
|
|
|
|
|
"name" integer not null,
|
|
|
|
|
"track_id" bigint,
|
|
|
|
|
"artist_id" bigint,
|
|
|
|
|
constraint "fk_track_artist_link_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
|
|
|
|
|
constraint "fk_track_artist_link_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "artist" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"name" text not null,
|
|
|
|
|
"sort_name" text not null,
|
|
|
|
|
"mbid" text not null
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "cluster" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"name" text not null,
|
|
|
|
|
"cluster_type_id" bigint,
|
|
|
|
|
constraint "fk_cluster_cluster_type" foreign key ("cluster_type_id") references "cluster_type" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "auth_token" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"value" text not null,
|
|
|
|
|
"expiry" text,
|
|
|
|
|
"user_id" bigint,
|
|
|
|
|
constraint "fk_auth_token_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "tracklist" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"name" text not null,
|
|
|
|
|
"type" integer not null,
|
|
|
|
|
"public" boolean not null,
|
|
|
|
|
"user_id" bigint,
|
|
|
|
|
constraint "fk_tracklist_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "track_cluster" (
|
|
|
|
|
"track_id" bigint,
|
|
|
|
|
"cluster_id" bigint,
|
|
|
|
|
primary key ("track_id", "cluster_id"),
|
|
|
|
|
constraint "fk_track_cluster_key1" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
|
|
|
|
|
constraint "fk_track_cluster_key2" foreign key ("cluster_id") references "cluster" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "track_bookmark" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"offset" integer,
|
|
|
|
|
"comment" text not null,
|
|
|
|
|
"track_id" bigint,
|
|
|
|
|
"user_id" bigint,
|
|
|
|
|
constraint "fk_track_bookmark_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
|
|
|
|
|
constraint "fk_track_bookmark_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "release" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"name" text not null,
|
|
|
|
|
"mbid" text not null
|
|
|
|
|
);
|
|
|
|
|
CREATE INDEX "track_cluster_track" on "track_cluster" ("track_id");
|
|
|
|
|
CREATE INDEX "track_cluster_cluster" on "track_cluster" ("cluster_id");
|
|
|
|
|
CREATE INDEX artist_name_idx ON artist(name);
|
|
|
|
|
CREATE INDEX artist_sort_name_nocase_idx ON artist(sort_name COLLATE NOCASE);
|
|
|
|
|
CREATE INDEX artist_mbid_idx ON artist(mbid);
|
|
|
|
|
CREATE INDEX auth_token_user_idx ON auth_token(user_id);
|
|
|
|
|
CREATE INDEX auth_token_expiry_idx ON auth_token(expiry);
|
|
|
|
|
CREATE INDEX auth_token_value_idx ON auth_token(value);
|
|
|
|
|
CREATE INDEX cluster_name_idx ON cluster(name);
|
|
|
|
|
CREATE INDEX cluster_cluster_type_idx ON cluster(cluster_type_id);
|
|
|
|
|
CREATE INDEX cluster_type_name_idx ON cluster_type(name);
|
|
|
|
|
CREATE INDEX tracklist_name_idx ON tracklist(name);
|
|
|
|
|
CREATE INDEX tracklist_user_idx ON tracklist(user_id);
|
|
|
|
|
CREATE INDEX track_features_track_idx ON track_features(track_id);
|
|
|
|
|
CREATE INDEX track_artist_link_artist_idx ON track_artist_link(artist_id);
|
|
|
|
|
CREATE INDEX track_artist_link_name_idx ON track_artist_link(name);
|
|
|
|
|
CREATE INDEX track_artist_link_track_idx ON track_artist_link(track_id);
|
|
|
|
|
CREATE INDEX track_artist_link_type_idx ON track_artist_link(type);
|
|
|
|
|
CREATE INDEX track_bookmark_track_idx ON track_bookmark(track_id);
|
|
|
|
|
CREATE INDEX track_bookmark_user_idx ON track_bookmark(user_id);
|
|
|
|
|
CREATE INDEX track_bookmark_user_track_idx ON track_bookmark(user_id,track_id);
|
|
|
|
|
CREATE INDEX release_name_idx ON release(name);
|
|
|
|
|
CREATE INDEX release_name_nocase_idx ON release(name COLLATE NOCASE);
|
|
|
|
|
CREATE INDEX release_mbid_idx ON release(mbid);
|
|
|
|
|
CREATE INDEX tracklist_entry_track_idx ON tracklist_entry(track_id);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "user" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"type" integer not null,
|
|
|
|
|
"login_name" text not null,
|
|
|
|
|
"password_salt" text not null,
|
|
|
|
|
"password_hash" text not null,
|
|
|
|
|
"last_login" text,
|
|
|
|
|
"subsonic_transcode_enable" boolean not null,
|
|
|
|
|
"subsonic_transcode_format" integer not null,
|
|
|
|
|
"subsonic_transcode_bitrate" integer not null,
|
|
|
|
|
"subsonic_artist_list_mode" integer not null,
|
|
|
|
|
"ui_theme" integer not null,
|
|
|
|
|
"cur_playing_track_pos" integer not null,
|
|
|
|
|
"repeat_all" boolean not null,
|
|
|
|
|
"radio" boolean not null
|
|
|
|
|
, listenbrainz_token TEXT, scrobbler INTEGER NOT NULL DEFAULT(0));
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "track" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"scan_version" integer not null,
|
|
|
|
|
"track_number" integer not null,
|
|
|
|
|
"disc_number" integer not null,
|
|
|
|
|
"name" text not null,
|
|
|
|
|
"duration" integer,
|
|
|
|
|
"date" integer text,
|
|
|
|
|
"original_date" integer text,
|
|
|
|
|
"file_path" text not null,
|
|
|
|
|
"file_last_write" text,
|
|
|
|
|
"file_added" text,
|
|
|
|
|
"has_cover" boolean not null,
|
|
|
|
|
"mbid" text not null,
|
|
|
|
|
"copyright" text not null,
|
|
|
|
|
"copyright_url" text not null,
|
|
|
|
|
"release_id" bigint, total_disc INTEGER NOT NULL DEFAULT(0), total_track INTEGER NOT NULL DEFAULT(0), track_replay_gain REAL, release_replay_gain REAL, disc_subtitle TEXT NOT NULL DEFAULT '', recording_mbid TEXT,
|
|
|
|
|
constraint "fk_track_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE INDEX track_file_last_write_idx ON track(file_last_write);
|
|
|
|
|
CREATE INDEX track_path_idx ON track(file_path);
|
|
|
|
|
CREATE INDEX track_name_idx ON track(name);
|
|
|
|
|
CREATE INDEX track_name_nocase_idx ON track(name COLLATE NOCASE);
|
|
|
|
|
CREATE INDEX track_mbid_idx ON track(mbid);
|
|
|
|
|
CREATE INDEX track_recording_mbid_idx ON track(recording_mbid);
|
|
|
|
|
CREATE INDEX track_release_idx ON track(release_id);
|
|
|
|
|
CREATE INDEX track_date_idx ON track(date);
|
|
|
|
|
CREATE INDEX track_original_date_idx ON track(original_date);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "starred_artist" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"scrobbler" integer not null,
|
|
|
|
|
"date_time" text,
|
|
|
|
|
"artist_id" bigint,
|
|
|
|
|
"user_id" bigint,
|
|
|
|
|
constraint "fk_starred_artist_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred,
|
|
|
|
|
constraint "fk_starred_artist_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "starred_release" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"scrobbler" integer not null,
|
|
|
|
|
"date_time" text,
|
|
|
|
|
"release_id" bigint,
|
|
|
|
|
"user_id" bigint,
|
|
|
|
|
constraint "fk_starred_release_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred,
|
|
|
|
|
constraint "fk_starred_release_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "starred_track" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"scrobbler" integer not null,
|
|
|
|
|
"date_time" text,
|
|
|
|
|
"track_id" bigint,
|
|
|
|
|
"user_id" bigint,
|
|
|
|
|
constraint "fk_starred_track_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
|
|
|
|
|
constraint "fk_starred_track_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS "listen" (
|
|
|
|
|
"id" integer primary key autoincrement,
|
|
|
|
|
"version" integer not null,
|
|
|
|
|
"date_time" text,
|
|
|
|
|
"scrobbler" integer not null,
|
|
|
|
|
"scrobbling_state" integer not null,
|
|
|
|
|
"track_id" bigint,
|
|
|
|
|
"user_id" bigint,
|
|
|
|
|
constraint "fk_listen_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
|
|
|
|
|
constraint "fk_listen_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
|
|
|
|
|
);
|
|
|
|
|
CREATE INDEX listen_scrobbler_idx ON listen(scrobbler);
|
|
|
|
|
CREATE INDEX listen_user_scrobbler_idx ON listen(user_id,scrobbler);
|
|
|
|
|
CREATE INDEX starred_artist_user_scrobbler_idx ON starred_artist(user_id,scrobbler);
|
|
|
|
|
CREATE INDEX starred_release_user_scrobbler_idx ON starred_release(user_id,scrobbler);
|
|
|
|
|
CREATE INDEX starred_track_user_scrobbler_idx ON starred_track(user_id,scrobbler);)" };
|
|
|
|
|
|
|
|
|
|
const std::string_view createDummyData{R"(
|
|
|
|
|
-- Inserting artists
|
|
|
|
|
INSERT INTO artist (version, name, sort_name, mbid) VALUES
|
|
|
|
|
(1, 'Artist A', 'Artist A', 'mbid_artist_a'),
|
|
|
|
|
(2, 'Artist B', 'Artist B', 'mbid_artist_b');
|
|
|
|
|
|
|
|
|
|
-- Inserting releases
|
|
|
|
|
INSERT INTO release (version, name, mbid) VALUES
|
|
|
|
|
(1, 'Release X', 'mbid_release_x'),
|
|
|
|
|
(2, 'Release Y', 'mbid_release_y');
|
|
|
|
|
|
|
|
|
|
-- Inserting tracks without any associated artists or releases (Orphan Tracks)
|
|
|
|
|
INSERT INTO track (version, scan_version, track_number, disc_number, name, duration, date, original_date, file_path, file_last_write, file_added, has_cover, mbid, recording_mbid, copyright, copyright_url)
|
|
|
|
|
VALUES
|
|
|
|
|
(1, 1, 1, 1, 'Orphan Track 1', 180, '2024-05-08', '2024-05-08', '/path/to/orphan_track_1.mp3', '2024-05-08', '2024-05-08', 1, 'mbid_orphan_track_1', 'mbid_recording_orphan_track_1', 'Copyright 2024', 'http://example.com/copyright'),
|
|
|
|
|
(2, 1, 2, 1, 'Orphan Track 2', 210, '2024-05-08', '2024-05-08', '/path/to/orphan_track_2.mp3', '2024-05-08', '2024-05-08', 1, 'mbid_orphan_track_2', 'mbid_recording_orphan_track_2', 'Copyright 2024', 'http://example.com/copyright');
|
|
|
|
|
|
|
|
|
|
-- Inserting tracks with artists but no releases
|
|
|
|
|
INSERT INTO track (version, scan_version, track_number, disc_number, name, duration, date, original_date, file_path, file_last_write, file_added, has_cover, mbid, recording_mbid, copyright, copyright_url)
|
|
|
|
|
VALUES
|
|
|
|
|
(1, 1, 3, 1, 'Track with Artist A', 200, '2024-05-08', '2024-05-08', '/path/to/track_with_artist_a.mp3', '2024-05-08', '2024-05-08', 1, 'mbid_track_with_artist_a', 'mbid_recording_track_with_artist_a', 'Copyright 2024', 'http://example.com/copyright'),
|
|
|
|
|
(2, 1, 4, 1, 'Track with Artist B', 220, '2024-05-08', '2024-05-08', '/path/to/track_with_artist_b.mp3', '2024-05-08', '2024-05-08', 1, 'mbid_track_with_artist_b', 'mbid_recording_track_with_artist_b', 'Copyright 2024', 'http://example.com/copyright');
|
|
|
|
|
|
|
|
|
|
-- Inserting tracks with artists linked
|
|
|
|
|
INSERT INTO track_artist_link (version, type, name, track_id, artist_id)
|
|
|
|
|
VALUES
|
|
|
|
|
(1, 1, 'Artist A', 3, 1),
|
|
|
|
|
(2, 1, 'Artist B', 4, 2);
|
|
|
|
|
|
|
|
|
|
-- Inserting tracks with artists and releases
|
|
|
|
|
INSERT INTO track (version, scan_version, track_number, disc_number, name, duration, date, original_date, file_path, file_last_write, file_added, has_cover, mbid, recording_mbid, copyright, copyright_url, release_id, total_disc, total_track, track_replay_gain, release_replay_gain)
|
|
|
|
|
VALUES
|
|
|
|
|
(1, 1, 5, 1, 'Track with Artist and Release X', 180, '2024-05-08', '2024-05-08', '/path/to/track_with_artist_and_release_x.mp3', '2024-05-08', '2024-05-08', 1, 'mbid_track_with_artist_and_release_x', 'mbid_recording_track_with_artist_and_release_x', 'Copyright 2024', 'http://example.com/copyright', 1, 1, 10, 0, 0),
|
|
|
|
|
(1, 1, 6, 1, 'Track with Artist and Release Y', 210, '2024-05-08', '2024-05-08', '/path/to/track_with_artist_and_release_y.mp3', '2024-05-08', '2024-05-08', 1, 'mbid_track_with_artist_and_release_y', 'mbid_recording_track_with_artist_and_release_y', 'Copyright 2024', 'http://example.com/copyright', 1, 1, 10, 0, 0);
|
|
|
|
|
|
|
|
|
|
-- Inserting tracks with artists and releases linked
|
|
|
|
|
INSERT INTO track_artist_link (version, type, name, track_id, artist_id)
|
|
|
|
|
VALUES
|
|
|
|
|
(1, 1, 'Artist A', 5, 1),
|
|
|
|
|
(2, 1, 'Artist B', 6, 2);)"};
|
|
|
|
|
|
|
|
|
|
Session session{ db };
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto transaction{ session.createWriteTransaction() };
|
|
|
|
|
|
|
|
|
|
executeStatements(session, schemaV33);
|
|
|
|
|
session.execute("INSERT INTO version_info (version, db_version) VALUES (1, 33)");
|
|
|
|
|
|
|
|
|
|
// Insert some base data
|
|
|
|
|
executeStatements(session, createDummyData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now perform full migration
|
|
|
|
|
db.getTLSSession().migrateSchemaIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
}
|