diff --git a/CMakeLists.txt b/CMakeLists.txt index 82f0b1a..d25c375 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.10) project(libdjinterop - VERSION 0.22.0 + VERSION 0.22.1 DESCRIPTION "C++ library providing access to DJ record libraries") set(PROJECT_HOMEPAGE_URL "https://github.com/xsco/libdjinterop") @@ -60,6 +60,8 @@ add_library( src/djinterop/engine/schema/schema_2_20_2.cpp src/djinterop/engine/schema/schema_2_20_3.cpp src/djinterop/engine/schema/schema_2_21_0.cpp + src/djinterop/engine/schema/schema_2_21_1.cpp + src/djinterop/engine/schema/schema_2_21_2.cpp src/djinterop/engine/schema/schema.cpp src/djinterop/engine/v1/engine_crate_impl.cpp src/djinterop/engine/v1/engine_database_impl.cpp diff --git a/include/djinterop/engine/engine.hpp b/include/djinterop/engine/engine.hpp index 4807cc3..bcd7509 100644 --- a/include/djinterop/engine/engine.hpp +++ b/include/djinterop/engine/engine.hpp @@ -137,23 +137,43 @@ constexpr const engine_version os_3_0_0{ semantic_version{3, 0, 0}, engine_database_type::os, "Engine DJ OS 3.0.0 to 3.0.1", semantic_version{2, 20, 3}}; -/// Engine DJ desktop 3.1.0 to 3.2.0 +/// Engine DJ desktop 3.1.0 to 3.4.0 constexpr const engine_version desktop_3_1_0{ semantic_version{3, 1, 0}, engine_database_type::desktop, "Engine DJ Desktop 3.1.0 to 3.4.0", semantic_version{2, 21, 0}}; /// Engine DJ OS 3.1.0 to 3.2.0 constexpr const engine_version os_3_1_0{ - semantic_version{3, 1, 0}, engine_database_type::desktop, + semantic_version{3, 1, 0}, engine_database_type::os, "Engine DJ OS 3.1.0 to 3.4.0", semantic_version{2, 21, 0}}; +/// Engine DJ desktop 4.0.0 +constexpr const engine_version desktop_4_0_0{ + semantic_version{4, 0, 0}, engine_database_type::desktop, + "Engine DJ Desktop 4.0.0", semantic_version{2, 21, 1}}; + +/// Engine DJ OS 4.0.0 +constexpr const engine_version os_4_0_0{ + semantic_version{4, 0, 0}, engine_database_type::os, + "Engine DJ OS 4.0.0", semantic_version{2, 21, 1}}; + +/// Engine DJ desktop 4.0.1 +constexpr const engine_version desktop_4_0_1{ + semantic_version{4, 0, 1}, engine_database_type::desktop, + "Engine DJ Desktop 4.0.1", semantic_version{2, 21, 2}}; + +/// Engine DJ OS 4.0.1 +constexpr const engine_version os_4_0_1{ + semantic_version{4, 0, 1}, engine_database_type::os, + "Engine DJ OS 4.0.1", semantic_version{2, 21, 2}}; + /// Set of available versions. -constexpr const std::array all_versions{ +constexpr const std::array all_versions{ os_1_0_0, os_1_0_3, desktop_1_1_1, os_1_2_0, os_1_2_2, desktop_1_2_2, os_1_3_1, os_1_4_0, os_1_5_1, desktop_1_5_1, os_1_6_0, desktop_2_0_0, os_2_0_0, desktop_2_2_0, os_2_2_0, desktop_2_4_0, os_2_4_0, desktop_3_0_0, os_3_0_0, desktop_3_1_0, - os_3_1_0}; + os_3_1_0, desktop_4_0_0, os_4_0_0, desktop_4_0_1, os_4_0_1}; /// Set of available V1 versions. constexpr const std::array all_v1_versions{ @@ -161,18 +181,19 @@ constexpr const std::array all_v1_versions{ os_1_3_1, os_1_4_0, os_1_5_1, desktop_1_5_1, os_1_6_0}; /// Set of available V2 versions. -constexpr const std::array all_v2_versions{ +constexpr const std::array all_v2_versions{ desktop_2_0_0, os_2_0_0, desktop_2_2_0, os_2_2_0, desktop_2_4_0, - os_2_4_0, desktop_3_0_0, os_3_0_0, desktop_3_1_0, os_3_1_0}; + os_2_4_0, desktop_3_0_0, os_3_0_0, desktop_3_1_0, os_3_1_0, + desktop_4_0_0, os_4_0_0, desktop_4_0_1, os_4_0_1}; /// The most recent version supported by the library. -constexpr engine_version latest = os_3_1_0; +constexpr engine_version latest = os_4_0_1; /// The most recent V2 version supported by the library. -constexpr engine_version latest_v2 = os_3_1_0; +constexpr engine_version latest_v2 = os_4_0_1; /// The most recent OS-type version supported by the library. -constexpr engine_version latest_os = os_3_1_0; +constexpr engine_version latest_os = os_4_0_1; namespace standard_pad_colors { diff --git a/src/djinterop/engine/schema/schema.cpp b/src/djinterop/engine/schema/schema.cpp index 87fe318..24acf7a 100644 --- a/src/djinterop/engine/schema/schema.cpp +++ b/src/djinterop/engine/schema/schema.cpp @@ -34,6 +34,8 @@ #include "schema_2_20_2.hpp" #include "schema_2_20_3.hpp" #include "schema_2_21_0.hpp" +#include "schema_2_21_1.hpp" +#include "schema_2_21_2.hpp" namespace djinterop::engine::schema { @@ -80,6 +82,16 @@ std::unique_ptr make_schema_creator_validator( return std::make_unique(); else if (version == desktop_3_1_0) return std::make_unique(); + else if (version == os_3_1_0) + return std::make_unique(); + else if (version == desktop_4_0_0) + return std::make_unique(); + else if (version == os_4_0_0) + return std::make_unique(); + else if (version == desktop_4_0_1) + return std::make_unique(); + else if (version == os_4_0_1) + return std::make_unique(); throw unsupported_engine_database{version.schema_version}; } diff --git a/src/djinterop/engine/schema/schema_2_21_1.cpp b/src/djinterop/engine/schema/schema_2_21_1.cpp new file mode 100644 index 0000000..13da73a --- /dev/null +++ b/src/djinterop/engine/schema/schema_2_21_1.cpp @@ -0,0 +1,519 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see . + */ +#include + +#include + +#include "../../util/random.hpp" +#include "schema_2_21_1.hpp" +#include "schema_validate_utils.hpp" + +namespace djinterop::engine::schema +{ +void schema_2_21_1::verify_track(sqlite::database& db) const +{ + // Schema 2.21.1 tweaks the `index_Track_bpmAnalyzed` index. + { + table_info cols{db, "Track"}; + auto iter = cols.begin(), end = cols.end(); + validate(iter, end, "activeOnLoadLoops", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "album", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "albumArt", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "albumArtId", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "artist", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "beatData", "BLOB", 0, "", 0); + ++iter; + validate(iter, end, "bitrate", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "bpm", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "bpmAnalyzed", "REAL", 0, "", 0); + ++iter; + validate(iter, end, "comment", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "composer", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "dateAdded", "DATETIME", 0, "", 0); + ++iter; + validate(iter, end, "dateCreated", "DATETIME", 0, "", 0); + ++iter; + validate(iter, end, "explicitLyrics", "BOOLEAN", 0, "", 0); + ++iter; + validate(iter, end, "fileBytes", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "fileType", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "filename", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "genre", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "id", "INTEGER", 0, "", 1); + ++iter; + validate(iter, end, "isAnalyzed", "BOOLEAN", 0, "", 0); + ++iter; + validate(iter, end, "isAvailable", "BOOLEAN", 0, "", 0); + ++iter; + validate(iter, end, "isBeatGridLocked", "BOOLEAN", 0, "", 0); + ++iter; + validate(iter, end, "isMetadataImported", "BOOLEAN", 0, "", 0); + ++iter; + validate( + iter, end, "isMetadataOfPackedTrackChanged", "BOOLEAN", 0, "", 0); + ++iter; + validate( + iter, end, "isPerfomanceDataOfPackedTrackChanged", "BOOLEAN", 0, "", + 0); + ++iter; + validate(iter, end, "isPlayed", "BOOLEAN", 0, "", 0); + ++iter; + validate(iter, end, "key", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "label", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "lastEditTime", "DATETIME", 0, "", 0); + ++iter; + validate(iter, end, "length", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "loops", "BLOB", 0, "", 0); + ++iter; + validate(iter, end, "originDatabaseUuid", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "originTrackId", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "overviewWaveFormData", "BLOB", 0, "", 0); + ++iter; + validate(iter, end, "path", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "pdbImportKey", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "playOrder", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "playedIndicator", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "quickCues", "BLOB", 0, "", 0); + ++iter; + validate(iter, end, "rating", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "remixer", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "streamingFlags", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "streamingSource", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "thirdPartySourceId", "INTEGER", 0, "", 0); + ++iter; + validate(iter, end, "timeLastPlayed", "DATETIME", 0, "", 0); + ++iter; + validate(iter, end, "title", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "trackData", "BLOB", 0, "", 0); + ++iter; + validate(iter, end, "uri", "TEXT", 0, "", 0); + ++iter; + validate(iter, end, "year", "INTEGER", 0, "", 0); + ++iter; + validate_no_more(iter, end); + } + { + index_list indices{db, "Track"}; + auto iter = indices.begin(), end = indices.end(); + validate(iter, end, "index_Track_album", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_albumArtId", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_artist", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_bpmAnalyzed", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_dateAdded", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_filename", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_genre", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_key", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_length", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_rating", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_title", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_uri", 0, "c", 0); + ++iter; + validate(iter, end, "index_Track_year", 0, "c", 0); + ++iter; + validate(iter, end, "sqlite_autoindex_Track_1", 1, "u", 0); + ++iter; + validate(iter, end, "sqlite_autoindex_Track_2", 1, "u", 0); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_album"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "album"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_albumArtId"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "albumArtId"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_artist"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "artist"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_bpmAnalyzed"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, ""); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_dateAdded"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "dateAdded"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_filename"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "filename"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_genre"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "genre"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_key"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "key"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_length"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "length"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_rating"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "rating"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_title"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "title"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_uri"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "uri"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "index_Track_year"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "year"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "sqlite_autoindex_Track_1"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "originDatabaseUuid"); + ++iter; + validate(iter, end, 1, "originTrackId"); + ++iter; + validate_no_more(iter, end); + } + { + index_info ii{db, "sqlite_autoindex_Track_2"}; + auto iter = ii.begin(), end = ii.end(); + validate(iter, end, 0, "path"); + ++iter; + validate_no_more(iter, end); + } +} + + +void schema_2_21_1::create(sqlite::database& db) +{ + db << "CREATE TABLE Information ( id INTEGER PRIMARY KEY AUTOINCREMENT, " + " uuid TEXT, schemaVersionMajor INTEGER, schemaVersionMinor " + "INTEGER, schemaVersionPatch INTEGER, " + "currentPlayedIndiciator INTEGER, " + "lastRekordBoxLibraryImportReadCounter INTEGER);"; + db << "CREATE TABLE AlbumArt ( id INTEGER PRIMARY KEY AUTOINCREMENT, " + "hash TEXT, albumArt BLOB );"; + db << "CREATE TABLE Pack ( id INTEGER PRIMARY KEY AUTOINCREMENT, packId " + "TEXT, changeLogDatabaseUuid TEXT, changeLogId INTEGER, " + "lastPackTime DATETIME );"; + db << "CREATE TABLE Track ( id INTEGER PRIMARY KEY AUTOINCREMENT, " + "playOrder INTEGER, length INTEGER, bpm INTEGER, year " + "INTEGER, path TEXT, filename TEXT, bitrate INTEGER, " + "bpmAnalyzed REAL, albumArtId INTEGER, fileBytes INTEGER, " + "title TEXT, artist TEXT, album TEXT, genre TEXT, " + "comment TEXT, label TEXT, composer TEXT, remixer TEXT, " + "key INTEGER, rating INTEGER, albumArt TEXT, timeLastPlayed " + "DATETIME, isPlayed BOOLEAN, fileType TEXT, isAnalyzed " + "BOOLEAN, dateCreated DATETIME, dateAdded DATETIME, " + "isAvailable BOOLEAN, isMetadataOfPackedTrackChanged BOOLEAN, " + " isPerfomanceDataOfPackedTrackChanged BOOLEAN, playedIndicator " + "INTEGER, isMetadataImported BOOLEAN, pdbImportKey INTEGER, " + " streamingSource TEXT, uri TEXT, isBeatGridLocked BOOLEAN, " + "originDatabaseUuid TEXT, originTrackId INTEGER, trackData " + "BLOB, overviewWaveFormData BLOB, beatData BLOB, quickCues " + "BLOB, loops BLOB, thirdPartySourceId INTEGER, " + "streamingFlags INTEGER, explicitLyrics BOOLEAN, " + "activeOnLoadLoops INTEGER, lastEditTime DATETIME, CONSTRAINT " + "C_originDatabaseUuid_originTrackId UNIQUE (originDatabaseUuid, " + "originTrackId), CONSTRAINT C_path UNIQUE (path), FOREIGN KEY " + "(albumArtId) REFERENCES AlbumArt (id) ON DELETE RESTRICT );"; + db << "CREATE TABLE Playlist ( id INTEGER PRIMARY KEY AUTOINCREMENT, " + "title TEXT, parentListId INTEGER, isPersisted BOOLEAN, " + "nextListId INTEGER, lastEditTime DATETIME, isExplicitlyExported " + "BOOLEAN, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, " + "parentListId), CONSTRAINT C_NEXT_LIST_ID_UNIQUE_FOR_PARENT UNIQUE " + "(parentListId, nextListId) );"; + db << "CREATE TABLE PlaylistEntity ( id INTEGER PRIMARY KEY " + "AUTOINCREMENT, listId INTEGER, trackId INTEGER, " + "databaseUuid TEXT, nextEntityId INTEGER, membershipReference " + "INTEGER, CONSTRAINT C_NAME_UNIQUE_FOR_LIST UNIQUE (listId, " + "databaseUuid, trackId), FOREIGN KEY (listId) REFERENCES Playlist " + "(id) ON DELETE CASCADE );"; + db << "CREATE TABLE PreparelistEntity ( id INTEGER PRIMARY KEY " + "AUTOINCREMENT, trackId INTEGER, trackNumber INTEGER, " + "FOREIGN KEY (trackId) REFERENCES Track (id) ON DELETE CASCADE );"; + db << "CREATE TABLE Smartlist ( listUuid TEXT NOT NULL PRIMARY KEY, " + " title TEXT, parentPlaylistPath TEXT, nextPlaylistPath TEXT, " + " nextListUuid TEXT, rules TEXT, lastEditTime DATETIME, " + "CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, " + "parentPlaylistPath), CONSTRAINT C_NEXT_LIST_UNIQUE_FOR_PARENT " + "UNIQUE (parentPlaylistPath, nextPlaylistPath, nextListUuid) );"; + db << "CREATE INDEX index_AlbumArt_hash ON AlbumArt (hash);"; + db << "CREATE TRIGGER trigger_after_insert_Pack_timestamp AFTER INSERT ON " + "Pack FOR EACH ROW WHEN NEW.lastPackTime IS NULL BEGIN UPDATE " + "Pack SET lastPackTime = strftime('%s') WHERE ROWID = NEW.ROWID; " + "END;"; + db << "CREATE TRIGGER trigger_after_insert_Pack_changeLogId AFTER INSERT " + "ON Pack FOR EACH ROW WHEN NEW.changeLogId = 0 BEGIN UPDATE Pack " + "SET changeLogId = 1 WHERE ROWID = NEW.ROWID; END;"; + db << "CREATE VIEW ChangeLog (id, trackId) AS SELECT 0, 0 WHERE FALSE;"; + db << "CREATE INDEX index_Track_filename ON Track (filename);"; + db << "CREATE INDEX index_Track_albumArtId ON Track (albumArtId);"; + db << "CREATE INDEX index_Track_uri ON Track (uri);"; + db << "CREATE INDEX index_Track_title ON Track(title);"; + db << "CREATE INDEX index_Track_length ON Track(length);"; + db << "CREATE INDEX index_Track_rating ON Track(rating);"; + db << "CREATE INDEX index_Track_year ON Track(year);"; + db << "CREATE INDEX index_Track_dateAdded ON Track(dateAdded);"; + db << "CREATE INDEX index_Track_genre ON Track(genre);"; + db << "CREATE INDEX index_Track_artist ON Track(artist);"; + db << "CREATE INDEX index_Track_album ON Track(album);"; + db << "CREATE INDEX index_Track_key ON Track(key);"; + db << "CREATE TRIGGER trigger_after_insert_Track_check_id AFTER INSERT ON " + "Track WHEN NEW.id <= (SELECT seq FROM sqlite_sequence WHERE name " + "= 'Track') BEGIN SELECT RAISE(ABORT, 'Recycling deleted track " + "id''s are not allowed'); END;"; + db << "CREATE TRIGGER trigger_after_update_Track_check_Id BEFORE UPDATE ON " + "Track WHEN NEW.id <> OLD.id BEGIN SELECT RAISE(ABORT, " + "'Changing track id''s are not allowed'); END;"; + db << "CREATE TRIGGER trigger_after_insert_Track_fix_origin AFTER INSERT " + "ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR " + "IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET " + " originTrackId = NEW.id, originDatabaseUuid = (SELECT " + "uuid FROM Information) WHERE track.id = NEW.id; END;"; + db << "CREATE TRIGGER trigger_after_update_Track_fix_origin AFTER UPDATE " + "ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR " + "IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET " + " originTrackId = NEW.id, originDatabaseUuid = (SELECT " + "uuid FROM Information) WHERE track.id = NEW.id; END;"; + db << "CREATE TRIGGER trigger_after_update_Track_timestamp AFTER UPDATE " + "OF length, bpm, year, filename, bitrate, bpmAnalyzed, albumArtId, " + " title, artist, album, genre, comment, label, composer, remixer, " + "key, rating, albumArt, fileType, isAnalyzed, isBeatgridLocked, " + "trackData, overviewWaveformData, beatData, quickCues, loops, " + "explicitLyrics, activeOnLoadLoops ON Track FOR EACH ROW BEGIN " + " UPDATE Track SET lastEditTime = strftime('%s') WHERE " + "ROWID=NEW.ROWID; END;"; + db << "CREATE VIEW PerformanceData AS SELECT id AS trackId, " + "isAnalyzed, trackData, overviewWaveFormData, beatData, " + "quickCues, loops, thirdPartySourceId, activeOnLoadLoops FROM " + "Track;"; + db << "CREATE TRIGGER " + "trigger_instead_update_thirdPartySourceId_PerformanceData INSTEAD " + "OF UPDATE OF thirdPartySourceId ON PerformanceData FOR EACH ROW " + "BEGIN UPDATE Track SET thirdPartySourceId = " + "NEW.thirdPartySourceId WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_insert_PerformanceData INSTEAD OF " + "INSERT ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET " + " isAnalyzed = NEW.isAnalyzed, trackData = NEW.trackData, " + " overviewWaveFormData = NEW.overviewWaveFormData, " + "beatData = NEW.beatData, quickCues = NEW.quickCues, " + "loops = NEW.loops, thirdPartySourceId = " + "NEW.thirdPartySourceId, activeOnLoadLoops = " + "NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_isAnalyzed_PerformanceData " + "INSTEAD OF UPDATE OF isAnalyzed ON PerformanceData FOR EACH ROW " + "BEGIN UPDATE Track SET isAnalyzed = NEW.isAnalyzed " + "WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_trackData_PerformanceData " + "INSTEAD OF UPDATE OF trackData ON PerformanceData FOR EACH ROW " + "BEGIN UPDATE Track SET trackData = NEW.trackData WHERE " + "Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER " + "trigger_instead_update_overviewWaveFormData_PerformanceData INSTEAD " + "OF UPDATE OF overviewWaveFormData ON PerformanceData FOR EACH ROW " + "BEGIN UPDATE Track SET overviewWaveFormData = " + "NEW.overviewWaveFormData WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_beatData_PerformanceData " + "INSTEAD OF UPDATE OF beatData ON PerformanceData FOR EACH ROW BEGIN " + " UPDATE Track SET beatData = NEW.beatData WHERE Track.id " + "= NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_quickCues_PerformanceData " + "INSTEAD OF UPDATE OF quickCues ON PerformanceData FOR EACH ROW " + "BEGIN UPDATE Track SET quickCues = NEW.quickCues WHERE " + "Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_loops_PerformanceData INSTEAD " + "OF UPDATE OF loops ON PerformanceData FOR EACH ROW BEGIN UPDATE " + "Track SET loops = NEW.loops WHERE Track.id = NEW.trackId; " + "END;"; + db << "CREATE TRIGGER trigger_instead_delete_PerformanceData INSTEAD OF " + "DELETE ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET " + " isAnalyzed = NULL, trackData = NULL, " + "overviewWaveFormData = NULL, beatData = NULL, " + "quickCues = NULL, loops = NULL, thirdPartySourceId = " + "NULL WHERE Track.id = OLD.trackId; END;"; + db << "CREATE TRIGGER " + "trigger_instead_update_activeOnLoadLoops_PerformanceData INSTEAD OF " + "UPDATE OF activeOnLoadLoops ON PerformanceData FOR EACH ROW BEGIN " + " UPDATE Track SET activeOnLoadLoops = NEW.activeOnLoadLoops " + " WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_before_insert_List BEFORE INSERT ON Playlist " + "FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = -(1 + " + "nextListId) WHERE nextListId = NEW.nextListId AND parentListId = " + "NEW.parentListId; END;"; + db << "CREATE TRIGGER trigger_after_insert_List AFTER INSERT ON Playlist " + "FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = " + "NEW.id WHERE nextListId = -(1 + NEW.nextListId) AND " + "parentListId = NEW.parentListId; END;"; + db << "CREATE TRIGGER trigger_after_delete_List AFTER DELETE ON Playlist " + "FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = " + "OLD.nextListId WHERE nextListId = OLD.id; DELETE FROM Playlist " + " WHERE parentListId = OLD.id; END;"; + db << "CREATE TRIGGER trigger_after_update_isPersistParent AFTER UPDATE ON " + "Playlist WHEN (old.isPersisted = 0 AND new.isPersisted = 1) " + " OR (old.parentListId != new.parentListId AND new.isPersisted = " + "1) BEGIN UPDATE Playlist SET isPersisted = 1 WHERE " + "id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); " + "END;"; + db << "CREATE TRIGGER trigger_after_update_isPersistChild AFTER UPDATE ON " + "Playlist WHEN old.isPersisted = 1 AND new.isPersisted = 0 " + "BEGIN UPDATE Playlist SET isPersisted = 0 WHERE id " + "IN (SELECT childListId FROM PlaylistAllChildren WHERE id=new.id); " + "END;"; + db << "CREATE TRIGGER trigger_after_insert_isPersist AFTER INSERT ON " + "Playlist WHEN new.isPersisted = 1 BEGIN UPDATE Playlist SET " + " isPersisted = 1 WHERE id IN (SELECT parentListId FROM " + "PlaylistAllParent WHERE id=new.id); END;"; + db << "CREATE VIEW PlaylistAllParent AS WITH FindAllParent AS ( SELECT " + "id, parentListId FROM Playlist UNION ALL SELECT " + "recursiveCTE.id, Plist.parentListId FROM Playlist Plist INNER JOIN " + "FindAllParent recursiveCTE ON recursiveCTE.parentListId = " + "Plist.id ) SELECT * FROM FindAllParent;"; + db << "CREATE VIEW PlaylistAllChildren AS WITH FindAllChild AS ( SELECT " + "id, id as childListId FROM Playlist UNION ALL SELECT " + "recursiveCTE.id, Plist.id FROM Playlist Plist INNER JOIN " + "FindAllChild recursiveCTE ON recursiveCTE.childListId = " + "Plist.parentListId ) SELECT * FROM FindAllChild WHERE id <> " + "childListId;"; + db << "CREATE VIEW PlaylistPath AS WITH RECURSIVE Heirarchy AS ( SELECT " + "id AS child, parentListId AS parent, title AS name, 1 AS depth FROM " + "Playlist UNION ALL SELECT child, parentListId AS parent, " + "title AS name, h.depth + 1 AS depth FROM Playlist c JOIN Heirarchy " + "h ON h.parent = c.id ORDER BY depth DESC ), OrderedList AS ( " + " SELECT id , nextListId, 1 AS position FROM Playlist WHERE " + "nextListId = 0 UNION ALL SELECT c.id , c.nextListId , " + "l.position + 1 FROM Playlist c INNER JOIN OrderedList l ON " + "c.nextListId = l.id ), NameConcat AS ( SELECT child AS id, " + " GROUP_CONCAT(name ,';') || ';' AS path FROM ( SELECT " + "child, name FROM Heirarchy ORDER BY depth DESC ) " + "GROUP BY child ) SELECT id, path, ROW_NUMBER() OVER ( " + " ORDER BY (SELECT COUNT(*) FROM (SELECT * FROM Heirarchy " + "WHERE child = id) ) DESC, (SELECT position FROM OrderedList " + "ol WHERE ol.id = c.id) ASC ) AS position FROM Playlist c LEFT " + "JOIN NameConcat g USING (id);"; + db << "CREATE INDEX index_PlaylistEntity_nextEntityId_listId ON " + "PlaylistEntity(nextEntityId, listId);"; + db << "CREATE TRIGGER trigger_before_delete_PlaylistEntity BEFORE DELETE " + "ON PlaylistEntity WHEN OLD.trackId > 0 BEGIN UPDATE " + "PlaylistEntity SET nextEntityId = OLD.nextEntityId WHERE " + "nextEntityId = OLD.id AND listId = OLD.listId; END;"; + db << "CREATE INDEX index_PreparelistEntity_trackId ON PreparelistEntity " + "(trackId);"; + db << "CREATE INDEX index_Track_bpmAnalyzed ON Track(CAST(bpmAnalyzed + " + "0.5 AS int));"; + + // Generate UUID for the Information table. + auto uuid_str = djinterop::util::generate_random_uuid(); + + // Not yet sure how the "currentPlayedIndiciator" (typo deliberate) value + // is formed. + auto current_played_indicator_fake_value = + djinterop::util::generate_random_int64(); + + // Insert row into Information + db << "INSERT INTO Information ([uuid], [schemaVersionMajor], " + "[schemaVersionMinor], [schemaVersionPatch], " + "[currentPlayedIndiciator], [lastRekordBoxLibraryImportReadCounter]) " + "VALUES (?, ?, ?, ?, ?, ?)" + << uuid_str << schema_version.maj << schema_version.min + << schema_version.pat << current_played_indicator_fake_value << 0; + + // Insert default album art entry + db << "INSERT INTO AlbumArt VALUES (1, '', NULL)"; +} + +} // namespace djinterop::engine::schema diff --git a/src/djinterop/engine/schema/schema_2_21_1.hpp b/src/djinterop/engine/schema/schema_2_21_1.hpp new file mode 100644 index 0000000..a58abd5 --- /dev/null +++ b/src/djinterop/engine/schema/schema_2_21_1.hpp @@ -0,0 +1,39 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see . + */ + +#pragma once + +#include + +#include + +#include "schema_2_21_0.hpp" + +namespace djinterop::engine::schema +{ +class schema_2_21_1 : public schema_2_21_0 +{ +public: + static constexpr const semantic_version schema_version{2, 21, 1}; + + void create(sqlite::database& db) override; + +protected: + void verify_track(sqlite::database& db) const override; +}; + +} // namespace djinterop::engine::schema diff --git a/src/djinterop/engine/schema/schema_2_21_2.cpp b/src/djinterop/engine/schema/schema_2_21_2.cpp new file mode 100644 index 0000000..f7e0d20 --- /dev/null +++ b/src/djinterop/engine/schema/schema_2_21_2.cpp @@ -0,0 +1,103 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see . + */ +#include + +#include + +#include "../../util/random.hpp" +#include "schema_2_21_2.hpp" +#include "schema_validate_utils.hpp" + +namespace djinterop::engine::schema +{ +void schema_2_21_2::create(sqlite::database& db) +{ + db << "CREATE TABLE Information ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, schemaVersionMajor INTEGER, schemaVersionMinor INTEGER, schemaVersionPatch INTEGER, currentPlayedIndiciator INTEGER, lastRekordBoxLibraryImportReadCounter INTEGER);"; + db << "CREATE TABLE AlbumArt ( id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT, albumArt BLOB );"; + db << "CREATE TABLE Pack ( id INTEGER PRIMARY KEY AUTOINCREMENT, packId TEXT, changeLogDatabaseUuid TEXT, changeLogId INTEGER, lastPackTime DATETIME );"; + db << "CREATE TABLE Track ( id INTEGER PRIMARY KEY AUTOINCREMENT, playOrder INTEGER, length INTEGER, bpm INTEGER, year INTEGER, path TEXT, filename TEXT, bitrate INTEGER, bpmAnalyzed REAL, albumArtId INTEGER, fileBytes INTEGER, title TEXT, artist TEXT, album TEXT, genre TEXT, comment TEXT, label TEXT, composer TEXT, remixer TEXT, key INTEGER, rating INTEGER, albumArt TEXT, timeLastPlayed DATETIME, isPlayed BOOLEAN, fileType TEXT, isAnalyzed BOOLEAN, dateCreated DATETIME, dateAdded DATETIME, isAvailable BOOLEAN, isMetadataOfPackedTrackChanged BOOLEAN, isPerfomanceDataOfPackedTrackChanged BOOLEAN, playedIndicator INTEGER, isMetadataImported BOOLEAN, pdbImportKey INTEGER, streamingSource TEXT, uri TEXT, isBeatGridLocked BOOLEAN, originDatabaseUuid TEXT, originTrackId INTEGER, trackData BLOB, overviewWaveFormData BLOB, beatData BLOB, quickCues BLOB, loops BLOB, thirdPartySourceId INTEGER, streamingFlags INTEGER, explicitLyrics BOOLEAN, activeOnLoadLoops INTEGER, lastEditTime DATETIME, CONSTRAINT C_originDatabaseUuid_originTrackId UNIQUE (originDatabaseUuid, originTrackId), CONSTRAINT C_path UNIQUE (path), FOREIGN KEY (albumArtId) REFERENCES AlbumArt (id) ON DELETE RESTRICT );"; + db << "CREATE TABLE Playlist ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, parentListId INTEGER, isPersisted BOOLEAN, nextListId INTEGER, lastEditTime DATETIME, isExplicitlyExported BOOLEAN, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentListId), CONSTRAINT C_NEXT_LIST_ID_UNIQUE_FOR_PARENT UNIQUE (parentListId, nextListId) );"; + db << "CREATE TABLE PlaylistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, listId INTEGER, trackId INTEGER, databaseUuid TEXT, nextEntityId INTEGER, membershipReference INTEGER, CONSTRAINT C_NAME_UNIQUE_FOR_LIST UNIQUE (listId, databaseUuid, trackId), FOREIGN KEY (listId) REFERENCES Playlist (id) ON DELETE CASCADE );"; + db << "CREATE TABLE PreparelistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, trackId INTEGER, trackNumber INTEGER, FOREIGN KEY (trackId) REFERENCES Track (id) ON DELETE CASCADE );"; + db << "CREATE TABLE Smartlist ( listUuid TEXT NOT NULL PRIMARY KEY, title TEXT, parentPlaylistPath TEXT, nextPlaylistPath TEXT, nextListUuid TEXT, rules TEXT, lastEditTime DATETIME, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentPlaylistPath), CONSTRAINT C_NEXT_LIST_UNIQUE_FOR_PARENT UNIQUE (parentPlaylistPath, nextPlaylistPath, nextListUuid) );"; + db << "CREATE INDEX index_AlbumArt_hash ON AlbumArt (hash);"; + db << "CREATE INDEX index_Track_filename ON Track (filename);"; + db << "CREATE INDEX index_Track_albumArtId ON Track (albumArtId);"; + db << "CREATE INDEX index_Track_uri ON Track (uri);"; + db << "CREATE INDEX index_Track_title ON Track(title);"; + db << "CREATE INDEX index_Track_length ON Track(length);"; + db << "CREATE INDEX index_Track_rating ON Track(rating);"; + db << "CREATE INDEX index_Track_year ON Track(year);"; + db << "CREATE INDEX index_Track_dateAdded ON Track(dateAdded);"; + db << "CREATE INDEX index_Track_genre ON Track(genre);"; + db << "CREATE INDEX index_Track_artist ON Track(artist);"; + db << "CREATE INDEX index_Track_album ON Track(album);"; + db << "CREATE INDEX index_Track_key ON Track(key);"; + db << "CREATE INDEX index_PlaylistEntity_nextEntityId_listId ON PlaylistEntity(nextEntityId, listId);"; + db << "CREATE INDEX index_PreparelistEntity_trackId ON PreparelistEntity (trackId);"; + db << "CREATE TRIGGER trigger_after_insert_Pack_timestamp AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.lastPackTime IS NULL BEGIN UPDATE Pack SET lastPackTime = strftime('%s') WHERE ROWID = NEW.ROWID; END;"; + db << "CREATE TRIGGER trigger_after_insert_Pack_changeLogId AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.changeLogId = 0 BEGIN UPDATE Pack SET changeLogId = 1 WHERE ROWID = NEW.ROWID; END;"; + db << "CREATE VIEW ChangeLog (id, trackId) AS SELECT 0, 0 WHERE FALSE;"; + db << "CREATE TRIGGER trigger_after_insert_Track_check_id AFTER INSERT ON Track WHEN NEW.id <= (SELECT seq FROM sqlite_sequence WHERE name = 'Track') BEGIN SELECT RAISE(ABORT, 'Recycling deleted track id''s are not allowed'); END;"; + db << "CREATE TRIGGER trigger_after_update_Track_check_Id BEFORE UPDATE ON Track WHEN NEW.id <> OLD.id BEGIN SELECT RAISE(ABORT, 'Changing track id''s are not allowed'); END;"; + db << "CREATE TRIGGER trigger_after_insert_Track_fix_origin AFTER INSERT ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END;"; + db << "CREATE TRIGGER trigger_after_update_Track_fix_origin AFTER UPDATE ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END;"; + db << "CREATE TRIGGER trigger_after_update_Track_timestamp AFTER UPDATE OF length, bpm, year, filename, bitrate, bpmAnalyzed, albumArtId, title, artist, album, genre, comment, label, composer, remixer, key, rating, albumArt, fileType, isAnalyzed, isBeatgridLocked, trackData, overviewWaveformData, beatData, quickCues, loops, explicitLyrics, activeOnLoadLoops ON Track FOR EACH ROW BEGIN UPDATE Track SET lastEditTime = strftime('%s') WHERE ROWID=NEW.ROWID; END;"; + db << "CREATE VIEW PerformanceData AS SELECT id AS trackId, isAnalyzed, trackData, overviewWaveFormData, beatData, quickCues, loops, thirdPartySourceId, activeOnLoadLoops FROM Track;"; + db << "CREATE TRIGGER trigger_instead_update_thirdPartySourceId_PerformanceData INSTEAD OF UPDATE OF thirdPartySourceId ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET thirdPartySourceId = NEW.thirdPartySourceId WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_isAnalyzed_PerformanceData INSTEAD OF UPDATE OF isAnalyzed ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NEW.isAnalyzed WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_trackData_PerformanceData INSTEAD OF UPDATE OF trackData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET trackData = NEW.trackData WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_overviewWaveFormData_PerformanceData INSTEAD OF UPDATE OF overviewWaveFormData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET overviewWaveFormData = NEW.overviewWaveFormData WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_beatData_PerformanceData INSTEAD OF UPDATE OF beatData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET beatData = NEW.beatData WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_quickCues_PerformanceData INSTEAD OF UPDATE OF quickCues ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET quickCues = NEW.quickCues WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_loops_PerformanceData INSTEAD OF UPDATE OF loops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET loops = NEW.loops WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_delete_PerformanceData INSTEAD OF DELETE ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NULL, trackData = NULL, overviewWaveFormData = NULL, beatData = NULL, quickCues = NULL, loops = NULL, thirdPartySourceId = NULL WHERE Track.id = OLD.trackId; END;"; + db << "CREATE TRIGGER trigger_instead_update_activeOnLoadLoops_PerformanceData INSTEAD OF UPDATE OF activeOnLoadLoops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END;"; + db << "CREATE TRIGGER trigger_before_insert_List BEFORE INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = -(1 + nextListId) WHERE nextListId = NEW.nextListId AND parentListId = NEW.parentListId; END;"; + db << "CREATE TRIGGER trigger_after_insert_List AFTER INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = NEW.id WHERE nextListId = -(1 + NEW.nextListId) AND parentListId = NEW.parentListId; END;"; + db << "CREATE TRIGGER trigger_after_delete_List AFTER DELETE ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = OLD.nextListId WHERE nextListId = OLD.id; DELETE FROM Playlist WHERE parentListId = OLD.id; END;"; + db << "CREATE TRIGGER trigger_after_update_isPersistParent AFTER UPDATE ON Playlist WHEN (old.isPersisted = 0 AND new.isPersisted = 1) OR (old.parentListId != new.parentListId AND new.isPersisted = 1) BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END;"; + db << "CREATE TRIGGER trigger_after_update_isPersistChild AFTER UPDATE ON Playlist WHEN old.isPersisted = 1 AND new.isPersisted = 0 BEGIN UPDATE Playlist SET isPersisted = 0 WHERE id IN (SELECT childListId FROM PlaylistAllChildren WHERE id=new.id); END;"; + db << "CREATE TRIGGER trigger_after_insert_isPersist AFTER INSERT ON Playlist WHEN new.isPersisted = 1 BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END;"; + db << "CREATE VIEW PlaylistAllParent AS WITH FindAllParent AS ( SELECT id, parentListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.parentListId FROM Playlist Plist INNER JOIN FindAllParent recursiveCTE ON recursiveCTE.parentListId = Plist.id ) SELECT * FROM FindAllParent;"; + db << "CREATE VIEW PlaylistAllChildren AS WITH FindAllChild AS ( SELECT id, id as childListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.id FROM Playlist Plist INNER JOIN FindAllChild recursiveCTE ON recursiveCTE.childListId = Plist.parentListId ) SELECT * FROM FindAllChild WHERE id <> childListId;"; + db << "CREATE VIEW PlaylistPath AS WITH RECURSIVE Heirarchy AS ( SELECT id AS child, parentListId AS parent, title AS name, 1 AS depth FROM Playlist UNION ALL SELECT child, parentListId AS parent, title AS name, h.depth + 1 AS depth FROM Playlist c JOIN Heirarchy h ON h.parent = c.id ORDER BY depth DESC ), OrderedList AS ( SELECT id , nextListId, 1 AS position FROM Playlist WHERE nextListId = 0 UNION ALL SELECT c.id , c.nextListId , l.position + 1 FROM Playlist c INNER JOIN OrderedList l ON c.nextListId = l.id ), NameConcat AS ( SELECT child AS id, GROUP_CONCAT(name ,';') || ';' AS path FROM ( SELECT child, name FROM Heirarchy ORDER BY depth DESC ) GROUP BY child ) SELECT id, path, ROW_NUMBER() OVER ( ORDER BY (SELECT COUNT(*) FROM (SELECT * FROM Heirarchy WHERE child = id) ) DESC, (SELECT position FROM OrderedList ol WHERE ol.id = c.id) ASC ) AS position FROM Playlist c LEFT JOIN NameConcat g USING (id);"; + db << "CREATE TRIGGER trigger_before_delete_PlaylistEntity BEFORE DELETE ON PlaylistEntity WHEN OLD.trackId > 0 BEGIN UPDATE PlaylistEntity SET nextEntityId = OLD.nextEntityId WHERE nextEntityId = OLD.id AND listId = OLD.listId; END;"; + db << "CREATE INDEX index_Track_bpmAnalyzed ON Track(CAST(bpmAnalyzed + 0.5 AS int));"; + db << "CREATE TRIGGER trigger_instead_insert_PerformanceData INSTEAD OF INSERT ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = IFNULL(NEW.isAnalyzed, isAnalyzed), trackData = NEW.trackData, overviewWaveFormData = NEW.overviewWaveFormData, beatData = NEW.beatData, quickCues = NEW.quickCues, loops = NEW.loops, thirdPartySourceId = NEW.thirdPartySourceId, activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END;"; + + // Generate UUID for the Information table. + auto uuid_str = djinterop::util::generate_random_uuid(); + + // Not yet sure how the "currentPlayedIndiciator" (typo deliberate) value + // is formed. + auto current_played_indicator_fake_value = + djinterop::util::generate_random_int64(); + + // Insert row into Information + db << "INSERT INTO Information ([uuid], [schemaVersionMajor], " + "[schemaVersionMinor], [schemaVersionPatch], " + "[currentPlayedIndiciator], [lastRekordBoxLibraryImportReadCounter]) " + "VALUES (?, ?, ?, ?, ?, ?)" + << uuid_str << schema_version.maj << schema_version.min + << schema_version.pat << current_played_indicator_fake_value << 0; + + // Insert default album art entry + db << "INSERT INTO AlbumArt VALUES (1, '', NULL)"; +} + +} // namespace djinterop::engine::schema diff --git a/src/djinterop/engine/schema/schema_2_21_2.hpp b/src/djinterop/engine/schema/schema_2_21_2.hpp new file mode 100644 index 0000000..48e0672 --- /dev/null +++ b/src/djinterop/engine/schema/schema_2_21_2.hpp @@ -0,0 +1,38 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see . + */ + +#pragma once + +#include + +#include + +#include "schema_2_21_1.hpp" + +namespace djinterop::engine::schema +{ +class schema_2_21_2 : public schema_2_21_1 +{ +public: + static constexpr const semantic_version schema_version{2, 21, 2}; + + void create(sqlite::database& db) override; + +protected: +}; + +} // namespace djinterop::engine::schema diff --git a/test/djinterop/engine/database_reference_test.cpp b/test/djinterop/engine/database_reference_test.cpp index aa4ed65..dd6dc7c 100644 --- a/test/djinterop/engine/database_reference_test.cpp +++ b/test/djinterop/engine/database_reference_test.cpp @@ -91,6 +91,10 @@ const std::vector ref_script_dirs{ "/ref/engine/desktop/desktop-3.3.0", "/ref/engine/sc5000/firmware-3.3.0", "/ref/engine/sc5000/firmware-3.4.0", + "/ref/engine/desktop/desktop-4.0.0", + "/ref/engine/sc5000/firmware-4.0.0", + "/ref/engine/desktop/desktop-4.0.1", + "/ref/engine/sc5000/firmware-4.0.1", }; } // anonymous namespace diff --git a/testdata/ref/engine/desktop/desktop-4.0.0/Database2/m.db.sql b/testdata/ref/engine/desktop/desktop-4.0.0/Database2/m.db.sql new file mode 100644 index 0000000..a3178e5 --- /dev/null +++ b/testdata/ref/engine/desktop/desktop-4.0.0/Database2/m.db.sql @@ -0,0 +1,59 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE Information ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, schemaVersionMajor INTEGER, schemaVersionMinor INTEGER, schemaVersionPatch INTEGER, currentPlayedIndiciator INTEGER, lastRekordBoxLibraryImportReadCounter INTEGER); +INSERT INTO Information VALUES(1,'43b432fc-8c09-40cd-8ef4-9fcb9a853f84',2,21,1,-3381190253225122247,NULL); +CREATE TABLE AlbumArt ( id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT, albumArt BLOB ); +CREATE TABLE Pack ( id INTEGER PRIMARY KEY AUTOINCREMENT, packId TEXT, changeLogDatabaseUuid TEXT, changeLogId INTEGER, lastPackTime DATETIME ); +CREATE TABLE Track ( id INTEGER PRIMARY KEY AUTOINCREMENT, playOrder INTEGER, length INTEGER, bpm INTEGER, year INTEGER, path TEXT, filename TEXT, bitrate INTEGER, bpmAnalyzed REAL, albumArtId INTEGER, fileBytes INTEGER, title TEXT, artist TEXT, album TEXT, genre TEXT, comment TEXT, label TEXT, composer TEXT, remixer TEXT, key INTEGER, rating INTEGER, albumArt TEXT, timeLastPlayed DATETIME, isPlayed BOOLEAN, fileType TEXT, isAnalyzed BOOLEAN, dateCreated DATETIME, dateAdded DATETIME, isAvailable BOOLEAN, isMetadataOfPackedTrackChanged BOOLEAN, isPerfomanceDataOfPackedTrackChanged BOOLEAN, playedIndicator INTEGER, isMetadataImported BOOLEAN, pdbImportKey INTEGER, streamingSource TEXT, uri TEXT, isBeatGridLocked BOOLEAN, originDatabaseUuid TEXT, originTrackId INTEGER, trackData BLOB, overviewWaveFormData BLOB, beatData BLOB, quickCues BLOB, loops BLOB, thirdPartySourceId INTEGER, streamingFlags INTEGER, explicitLyrics BOOLEAN, activeOnLoadLoops INTEGER, lastEditTime DATETIME, CONSTRAINT C_originDatabaseUuid_originTrackId UNIQUE (originDatabaseUuid, originTrackId), CONSTRAINT C_path UNIQUE (path), FOREIGN KEY (albumArtId) REFERENCES AlbumArt (id) ON DELETE RESTRICT ); +CREATE TABLE Playlist ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, parentListId INTEGER, isPersisted BOOLEAN, nextListId INTEGER, lastEditTime DATETIME, isExplicitlyExported BOOLEAN, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentListId), CONSTRAINT C_NEXT_LIST_ID_UNIQUE_FOR_PARENT UNIQUE (parentListId, nextListId) ); +CREATE TABLE PlaylistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, listId INTEGER, trackId INTEGER, databaseUuid TEXT, nextEntityId INTEGER, membershipReference INTEGER, CONSTRAINT C_NAME_UNIQUE_FOR_LIST UNIQUE (listId, databaseUuid, trackId), FOREIGN KEY (listId) REFERENCES Playlist (id) ON DELETE CASCADE ); +CREATE TABLE PreparelistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, trackId INTEGER, trackNumber INTEGER, FOREIGN KEY (trackId) REFERENCES Track (id) ON DELETE CASCADE ); +CREATE TABLE Smartlist ( listUuid TEXT NOT NULL PRIMARY KEY, title TEXT, parentPlaylistPath TEXT, nextPlaylistPath TEXT, nextListUuid TEXT, rules TEXT, lastEditTime DATETIME, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentPlaylistPath), CONSTRAINT C_NEXT_LIST_UNIQUE_FOR_PARENT UNIQUE (parentPlaylistPath, nextPlaylistPath, nextListUuid) ); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('Information',1); +CREATE INDEX index_AlbumArt_hash ON AlbumArt (hash); +CREATE TRIGGER trigger_after_insert_Pack_timestamp AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.lastPackTime IS NULL BEGIN UPDATE Pack SET lastPackTime = strftime('%s') WHERE ROWID = NEW.ROWID; END; +CREATE TRIGGER trigger_after_insert_Pack_changeLogId AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.changeLogId = 0 BEGIN UPDATE Pack SET changeLogId = 1 WHERE ROWID = NEW.ROWID; END; +CREATE VIEW ChangeLog (id, trackId) AS SELECT 0, 0 WHERE FALSE; +CREATE INDEX index_Track_filename ON Track (filename); +CREATE INDEX index_Track_albumArtId ON Track (albumArtId); +CREATE INDEX index_Track_uri ON Track (uri); +CREATE INDEX index_Track_title ON Track(title); +CREATE INDEX index_Track_length ON Track(length); +CREATE INDEX index_Track_rating ON Track(rating); +CREATE INDEX index_Track_year ON Track(year); +CREATE INDEX index_Track_dateAdded ON Track(dateAdded); +CREATE INDEX index_Track_genre ON Track(genre); +CREATE INDEX index_Track_artist ON Track(artist); +CREATE INDEX index_Track_album ON Track(album); +CREATE INDEX index_Track_key ON Track(key); +CREATE TRIGGER trigger_after_insert_Track_check_id AFTER INSERT ON Track WHEN NEW.id <= (SELECT seq FROM sqlite_sequence WHERE name = 'Track') BEGIN SELECT RAISE(ABORT, 'Recycling deleted track id''s are not allowed'); END; +CREATE TRIGGER trigger_after_update_Track_check_Id BEFORE UPDATE ON Track WHEN NEW.id <> OLD.id BEGIN SELECT RAISE(ABORT, 'Changing track id''s are not allowed'); END; +CREATE TRIGGER trigger_after_insert_Track_fix_origin AFTER INSERT ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END; +CREATE TRIGGER trigger_after_update_Track_fix_origin AFTER UPDATE ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END; +CREATE TRIGGER trigger_after_update_Track_timestamp AFTER UPDATE OF length, bpm, year, filename, bitrate, bpmAnalyzed, albumArtId, title, artist, album, genre, comment, label, composer, remixer, key, rating, albumArt, fileType, isAnalyzed, isBeatgridLocked, trackData, overviewWaveformData, beatData, quickCues, loops, explicitLyrics, activeOnLoadLoops ON Track FOR EACH ROW BEGIN UPDATE Track SET lastEditTime = strftime('%s') WHERE ROWID=NEW.ROWID; END; +CREATE VIEW PerformanceData AS SELECT id AS trackId, isAnalyzed, trackData, overviewWaveFormData, beatData, quickCues, loops, thirdPartySourceId, activeOnLoadLoops FROM Track; +CREATE TRIGGER trigger_instead_update_thirdPartySourceId_PerformanceData INSTEAD OF UPDATE OF thirdPartySourceId ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET thirdPartySourceId = NEW.thirdPartySourceId WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_insert_PerformanceData INSTEAD OF INSERT ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NEW.isAnalyzed, trackData = NEW.trackData, overviewWaveFormData = NEW.overviewWaveFormData, beatData = NEW.beatData, quickCues = NEW.quickCues, loops = NEW.loops, thirdPartySourceId = NEW.thirdPartySourceId, activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_isAnalyzed_PerformanceData INSTEAD OF UPDATE OF isAnalyzed ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NEW.isAnalyzed WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_trackData_PerformanceData INSTEAD OF UPDATE OF trackData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET trackData = NEW.trackData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_overviewWaveFormData_PerformanceData INSTEAD OF UPDATE OF overviewWaveFormData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET overviewWaveFormData = NEW.overviewWaveFormData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_beatData_PerformanceData INSTEAD OF UPDATE OF beatData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET beatData = NEW.beatData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_quickCues_PerformanceData INSTEAD OF UPDATE OF quickCues ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET quickCues = NEW.quickCues WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_loops_PerformanceData INSTEAD OF UPDATE OF loops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET loops = NEW.loops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_delete_PerformanceData INSTEAD OF DELETE ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NULL, trackData = NULL, overviewWaveFormData = NULL, beatData = NULL, quickCues = NULL, loops = NULL, thirdPartySourceId = NULL WHERE Track.id = OLD.trackId; END; +CREATE TRIGGER trigger_instead_update_activeOnLoadLoops_PerformanceData INSTEAD OF UPDATE OF activeOnLoadLoops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_before_insert_List BEFORE INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = -(1 + nextListId) WHERE nextListId = NEW.nextListId AND parentListId = NEW.parentListId; END; +CREATE TRIGGER trigger_after_insert_List AFTER INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = NEW.id WHERE nextListId = -(1 + NEW.nextListId) AND parentListId = NEW.parentListId; END; +CREATE TRIGGER trigger_after_delete_List AFTER DELETE ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = OLD.nextListId WHERE nextListId = OLD.id; DELETE FROM Playlist WHERE parentListId = OLD.id; END; +CREATE TRIGGER trigger_after_update_isPersistParent AFTER UPDATE ON Playlist WHEN (old.isPersisted = 0 AND new.isPersisted = 1) OR (old.parentListId != new.parentListId AND new.isPersisted = 1) BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END; +CREATE TRIGGER trigger_after_update_isPersistChild AFTER UPDATE ON Playlist WHEN old.isPersisted = 1 AND new.isPersisted = 0 BEGIN UPDATE Playlist SET isPersisted = 0 WHERE id IN (SELECT childListId FROM PlaylistAllChildren WHERE id=new.id); END; +CREATE TRIGGER trigger_after_insert_isPersist AFTER INSERT ON Playlist WHEN new.isPersisted = 1 BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END; +CREATE VIEW PlaylistAllParent AS WITH FindAllParent AS ( SELECT id, parentListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.parentListId FROM Playlist Plist INNER JOIN FindAllParent recursiveCTE ON recursiveCTE.parentListId = Plist.id ) SELECT * FROM FindAllParent; +CREATE VIEW PlaylistAllChildren AS WITH FindAllChild AS ( SELECT id, id as childListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.id FROM Playlist Plist INNER JOIN FindAllChild recursiveCTE ON recursiveCTE.childListId = Plist.parentListId ) SELECT * FROM FindAllChild WHERE id <> childListId; +CREATE VIEW PlaylistPath AS WITH RECURSIVE Heirarchy AS ( SELECT id AS child, parentListId AS parent, title AS name, 1 AS depth FROM Playlist UNION ALL SELECT child, parentListId AS parent, title AS name, h.depth + 1 AS depth FROM Playlist c JOIN Heirarchy h ON h.parent = c.id ORDER BY depth DESC ), OrderedList AS ( SELECT id , nextListId, 1 AS position FROM Playlist WHERE nextListId = 0 UNION ALL SELECT c.id , c.nextListId , l.position + 1 FROM Playlist c INNER JOIN OrderedList l ON c.nextListId = l.id ), NameConcat AS ( SELECT child AS id, GROUP_CONCAT(name ,';') || ';' AS path FROM ( SELECT child, name FROM Heirarchy ORDER BY depth DESC ) GROUP BY child ) SELECT id, path, ROW_NUMBER() OVER ( ORDER BY (SELECT COUNT(*) FROM (SELECT * FROM Heirarchy WHERE child = id) ) DESC, (SELECT position FROM OrderedList ol WHERE ol.id = c.id) ASC ) AS position FROM Playlist c LEFT JOIN NameConcat g USING (id); +CREATE INDEX index_PlaylistEntity_nextEntityId_listId ON PlaylistEntity(nextEntityId, listId); +CREATE TRIGGER trigger_before_delete_PlaylistEntity BEFORE DELETE ON PlaylistEntity WHEN OLD.trackId > 0 BEGIN UPDATE PlaylistEntity SET nextEntityId = OLD.nextEntityId WHERE nextEntityId = OLD.id AND listId = OLD.listId; END; +CREATE INDEX index_PreparelistEntity_trackId ON PreparelistEntity (trackId); +CREATE INDEX index_Track_bpmAnalyzed ON Track(CAST(bpmAnalyzed + 0.5 AS int)); +COMMIT; diff --git a/testdata/ref/engine/desktop/desktop-4.0.1/Database2/m.db.sql b/testdata/ref/engine/desktop/desktop-4.0.1/Database2/m.db.sql new file mode 100644 index 0000000..b47853b --- /dev/null +++ b/testdata/ref/engine/desktop/desktop-4.0.1/Database2/m.db.sql @@ -0,0 +1,59 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE Information ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, schemaVersionMajor INTEGER, schemaVersionMinor INTEGER, schemaVersionPatch INTEGER, currentPlayedIndiciator INTEGER, lastRekordBoxLibraryImportReadCounter INTEGER); +INSERT INTO Information VALUES(1,'43b432fc-8c09-40cd-8ef4-9fcb9a853f84',2,21,2,487013658914731601,NULL); +CREATE TABLE AlbumArt ( id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT, albumArt BLOB ); +CREATE TABLE Pack ( id INTEGER PRIMARY KEY AUTOINCREMENT, packId TEXT, changeLogDatabaseUuid TEXT, changeLogId INTEGER, lastPackTime DATETIME ); +CREATE TABLE Track ( id INTEGER PRIMARY KEY AUTOINCREMENT, playOrder INTEGER, length INTEGER, bpm INTEGER, year INTEGER, path TEXT, filename TEXT, bitrate INTEGER, bpmAnalyzed REAL, albumArtId INTEGER, fileBytes INTEGER, title TEXT, artist TEXT, album TEXT, genre TEXT, comment TEXT, label TEXT, composer TEXT, remixer TEXT, key INTEGER, rating INTEGER, albumArt TEXT, timeLastPlayed DATETIME, isPlayed BOOLEAN, fileType TEXT, isAnalyzed BOOLEAN, dateCreated DATETIME, dateAdded DATETIME, isAvailable BOOLEAN, isMetadataOfPackedTrackChanged BOOLEAN, isPerfomanceDataOfPackedTrackChanged BOOLEAN, playedIndicator INTEGER, isMetadataImported BOOLEAN, pdbImportKey INTEGER, streamingSource TEXT, uri TEXT, isBeatGridLocked BOOLEAN, originDatabaseUuid TEXT, originTrackId INTEGER, trackData BLOB, overviewWaveFormData BLOB, beatData BLOB, quickCues BLOB, loops BLOB, thirdPartySourceId INTEGER, streamingFlags INTEGER, explicitLyrics BOOLEAN, activeOnLoadLoops INTEGER, lastEditTime DATETIME, CONSTRAINT C_originDatabaseUuid_originTrackId UNIQUE (originDatabaseUuid, originTrackId), CONSTRAINT C_path UNIQUE (path), FOREIGN KEY (albumArtId) REFERENCES AlbumArt (id) ON DELETE RESTRICT ); +CREATE TABLE Playlist ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, parentListId INTEGER, isPersisted BOOLEAN, nextListId INTEGER, lastEditTime DATETIME, isExplicitlyExported BOOLEAN, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentListId), CONSTRAINT C_NEXT_LIST_ID_UNIQUE_FOR_PARENT UNIQUE (parentListId, nextListId) ); +CREATE TABLE PlaylistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, listId INTEGER, trackId INTEGER, databaseUuid TEXT, nextEntityId INTEGER, membershipReference INTEGER, CONSTRAINT C_NAME_UNIQUE_FOR_LIST UNIQUE (listId, databaseUuid, trackId), FOREIGN KEY (listId) REFERENCES Playlist (id) ON DELETE CASCADE ); +CREATE TABLE PreparelistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, trackId INTEGER, trackNumber INTEGER, FOREIGN KEY (trackId) REFERENCES Track (id) ON DELETE CASCADE ); +CREATE TABLE Smartlist ( listUuid TEXT NOT NULL PRIMARY KEY, title TEXT, parentPlaylistPath TEXT, nextPlaylistPath TEXT, nextListUuid TEXT, rules TEXT, lastEditTime DATETIME, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentPlaylistPath), CONSTRAINT C_NEXT_LIST_UNIQUE_FOR_PARENT UNIQUE (parentPlaylistPath, nextPlaylistPath, nextListUuid) ); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('Information',1); +CREATE INDEX index_AlbumArt_hash ON AlbumArt (hash); +CREATE INDEX index_Track_filename ON Track (filename); +CREATE INDEX index_Track_albumArtId ON Track (albumArtId); +CREATE INDEX index_Track_uri ON Track (uri); +CREATE INDEX index_Track_title ON Track(title); +CREATE INDEX index_Track_length ON Track(length); +CREATE INDEX index_Track_rating ON Track(rating); +CREATE INDEX index_Track_year ON Track(year); +CREATE INDEX index_Track_dateAdded ON Track(dateAdded); +CREATE INDEX index_Track_genre ON Track(genre); +CREATE INDEX index_Track_artist ON Track(artist); +CREATE INDEX index_Track_album ON Track(album); +CREATE INDEX index_Track_key ON Track(key); +CREATE INDEX index_PlaylistEntity_nextEntityId_listId ON PlaylistEntity(nextEntityId, listId); +CREATE INDEX index_PreparelistEntity_trackId ON PreparelistEntity (trackId); +CREATE TRIGGER trigger_after_insert_Pack_timestamp AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.lastPackTime IS NULL BEGIN UPDATE Pack SET lastPackTime = strftime('%s') WHERE ROWID = NEW.ROWID; END; +CREATE TRIGGER trigger_after_insert_Pack_changeLogId AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.changeLogId = 0 BEGIN UPDATE Pack SET changeLogId = 1 WHERE ROWID = NEW.ROWID; END; +CREATE VIEW ChangeLog (id, trackId) AS SELECT 0, 0 WHERE FALSE; +CREATE TRIGGER trigger_after_insert_Track_check_id AFTER INSERT ON Track WHEN NEW.id <= (SELECT seq FROM sqlite_sequence WHERE name = 'Track') BEGIN SELECT RAISE(ABORT, 'Recycling deleted track id''s are not allowed'); END; +CREATE TRIGGER trigger_after_update_Track_check_Id BEFORE UPDATE ON Track WHEN NEW.id <> OLD.id BEGIN SELECT RAISE(ABORT, 'Changing track id''s are not allowed'); END; +CREATE TRIGGER trigger_after_insert_Track_fix_origin AFTER INSERT ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END; +CREATE TRIGGER trigger_after_update_Track_fix_origin AFTER UPDATE ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END; +CREATE TRIGGER trigger_after_update_Track_timestamp AFTER UPDATE OF length, bpm, year, filename, bitrate, bpmAnalyzed, albumArtId, title, artist, album, genre, comment, label, composer, remixer, key, rating, albumArt, fileType, isAnalyzed, isBeatgridLocked, trackData, overviewWaveformData, beatData, quickCues, loops, explicitLyrics, activeOnLoadLoops ON Track FOR EACH ROW BEGIN UPDATE Track SET lastEditTime = strftime('%s') WHERE ROWID=NEW.ROWID; END; +CREATE VIEW PerformanceData AS SELECT id AS trackId, isAnalyzed, trackData, overviewWaveFormData, beatData, quickCues, loops, thirdPartySourceId, activeOnLoadLoops FROM Track; +CREATE TRIGGER trigger_instead_update_thirdPartySourceId_PerformanceData INSTEAD OF UPDATE OF thirdPartySourceId ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET thirdPartySourceId = NEW.thirdPartySourceId WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_isAnalyzed_PerformanceData INSTEAD OF UPDATE OF isAnalyzed ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NEW.isAnalyzed WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_trackData_PerformanceData INSTEAD OF UPDATE OF trackData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET trackData = NEW.trackData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_overviewWaveFormData_PerformanceData INSTEAD OF UPDATE OF overviewWaveFormData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET overviewWaveFormData = NEW.overviewWaveFormData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_beatData_PerformanceData INSTEAD OF UPDATE OF beatData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET beatData = NEW.beatData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_quickCues_PerformanceData INSTEAD OF UPDATE OF quickCues ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET quickCues = NEW.quickCues WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_loops_PerformanceData INSTEAD OF UPDATE OF loops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET loops = NEW.loops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_delete_PerformanceData INSTEAD OF DELETE ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NULL, trackData = NULL, overviewWaveFormData = NULL, beatData = NULL, quickCues = NULL, loops = NULL, thirdPartySourceId = NULL WHERE Track.id = OLD.trackId; END; +CREATE TRIGGER trigger_instead_update_activeOnLoadLoops_PerformanceData INSTEAD OF UPDATE OF activeOnLoadLoops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_before_insert_List BEFORE INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = -(1 + nextListId) WHERE nextListId = NEW.nextListId AND parentListId = NEW.parentListId; END; +CREATE TRIGGER trigger_after_insert_List AFTER INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = NEW.id WHERE nextListId = -(1 + NEW.nextListId) AND parentListId = NEW.parentListId; END; +CREATE TRIGGER trigger_after_delete_List AFTER DELETE ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = OLD.nextListId WHERE nextListId = OLD.id; DELETE FROM Playlist WHERE parentListId = OLD.id; END; +CREATE TRIGGER trigger_after_update_isPersistParent AFTER UPDATE ON Playlist WHEN (old.isPersisted = 0 AND new.isPersisted = 1) OR (old.parentListId != new.parentListId AND new.isPersisted = 1) BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END; +CREATE TRIGGER trigger_after_update_isPersistChild AFTER UPDATE ON Playlist WHEN old.isPersisted = 1 AND new.isPersisted = 0 BEGIN UPDATE Playlist SET isPersisted = 0 WHERE id IN (SELECT childListId FROM PlaylistAllChildren WHERE id=new.id); END; +CREATE TRIGGER trigger_after_insert_isPersist AFTER INSERT ON Playlist WHEN new.isPersisted = 1 BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END; +CREATE VIEW PlaylistAllParent AS WITH FindAllParent AS ( SELECT id, parentListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.parentListId FROM Playlist Plist INNER JOIN FindAllParent recursiveCTE ON recursiveCTE.parentListId = Plist.id ) SELECT * FROM FindAllParent; +CREATE VIEW PlaylistAllChildren AS WITH FindAllChild AS ( SELECT id, id as childListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.id FROM Playlist Plist INNER JOIN FindAllChild recursiveCTE ON recursiveCTE.childListId = Plist.parentListId ) SELECT * FROM FindAllChild WHERE id <> childListId; +CREATE VIEW PlaylistPath AS WITH RECURSIVE Heirarchy AS ( SELECT id AS child, parentListId AS parent, title AS name, 1 AS depth FROM Playlist UNION ALL SELECT child, parentListId AS parent, title AS name, h.depth + 1 AS depth FROM Playlist c JOIN Heirarchy h ON h.parent = c.id ORDER BY depth DESC ), OrderedList AS ( SELECT id , nextListId, 1 AS position FROM Playlist WHERE nextListId = 0 UNION ALL SELECT c.id , c.nextListId , l.position + 1 FROM Playlist c INNER JOIN OrderedList l ON c.nextListId = l.id ), NameConcat AS ( SELECT child AS id, GROUP_CONCAT(name ,';') || ';' AS path FROM ( SELECT child, name FROM Heirarchy ORDER BY depth DESC ) GROUP BY child ) SELECT id, path, ROW_NUMBER() OVER ( ORDER BY (SELECT COUNT(*) FROM (SELECT * FROM Heirarchy WHERE child = id) ) DESC, (SELECT position FROM OrderedList ol WHERE ol.id = c.id) ASC ) AS position FROM Playlist c LEFT JOIN NameConcat g USING (id); +CREATE TRIGGER trigger_before_delete_PlaylistEntity BEFORE DELETE ON PlaylistEntity WHEN OLD.trackId > 0 BEGIN UPDATE PlaylistEntity SET nextEntityId = OLD.nextEntityId WHERE nextEntityId = OLD.id AND listId = OLD.listId; END; +CREATE INDEX index_Track_bpmAnalyzed ON Track(CAST(bpmAnalyzed + 0.5 AS int)); +CREATE TRIGGER trigger_instead_insert_PerformanceData INSTEAD OF INSERT ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = IFNULL(NEW.isAnalyzed, isAnalyzed), trackData = NEW.trackData, overviewWaveFormData = NEW.overviewWaveFormData, beatData = NEW.beatData, quickCues = NEW.quickCues, loops = NEW.loops, thirdPartySourceId = NEW.thirdPartySourceId, activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END; +COMMIT; diff --git a/testdata/ref/engine/sc5000/firmware-4.0.0/Database2/m.db.sql b/testdata/ref/engine/sc5000/firmware-4.0.0/Database2/m.db.sql new file mode 100644 index 0000000..511d5b5 --- /dev/null +++ b/testdata/ref/engine/sc5000/firmware-4.0.0/Database2/m.db.sql @@ -0,0 +1,59 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE Information ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, schemaVersionMajor INTEGER, schemaVersionMinor INTEGER, schemaVersionPatch INTEGER, currentPlayedIndiciator INTEGER, lastRekordBoxLibraryImportReadCounter INTEGER); +INSERT INTO Information VALUES(1,'94baac4f-9a55-454c-b1ce-6423bbefd6bf',2,21,1,2322329081098966114,NULL); +CREATE TABLE AlbumArt ( id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT, albumArt BLOB ); +CREATE TABLE Pack ( id INTEGER PRIMARY KEY AUTOINCREMENT, packId TEXT, changeLogDatabaseUuid TEXT, changeLogId INTEGER, lastPackTime DATETIME ); +CREATE TABLE Track ( id INTEGER PRIMARY KEY AUTOINCREMENT, playOrder INTEGER, length INTEGER, bpm INTEGER, year INTEGER, path TEXT, filename TEXT, bitrate INTEGER, bpmAnalyzed REAL, albumArtId INTEGER, fileBytes INTEGER, title TEXT, artist TEXT, album TEXT, genre TEXT, comment TEXT, label TEXT, composer TEXT, remixer TEXT, key INTEGER, rating INTEGER, albumArt TEXT, timeLastPlayed DATETIME, isPlayed BOOLEAN, fileType TEXT, isAnalyzed BOOLEAN, dateCreated DATETIME, dateAdded DATETIME, isAvailable BOOLEAN, isMetadataOfPackedTrackChanged BOOLEAN, isPerfomanceDataOfPackedTrackChanged BOOLEAN, playedIndicator INTEGER, isMetadataImported BOOLEAN, pdbImportKey INTEGER, streamingSource TEXT, uri TEXT, isBeatGridLocked BOOLEAN, originDatabaseUuid TEXT, originTrackId INTEGER, trackData BLOB, overviewWaveFormData BLOB, beatData BLOB, quickCues BLOB, loops BLOB, thirdPartySourceId INTEGER, streamingFlags INTEGER, explicitLyrics BOOLEAN, activeOnLoadLoops INTEGER, lastEditTime DATETIME, CONSTRAINT C_originDatabaseUuid_originTrackId UNIQUE (originDatabaseUuid, originTrackId), CONSTRAINT C_path UNIQUE (path), FOREIGN KEY (albumArtId) REFERENCES AlbumArt (id) ON DELETE RESTRICT ); +CREATE TABLE Playlist ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, parentListId INTEGER, isPersisted BOOLEAN, nextListId INTEGER, lastEditTime DATETIME, isExplicitlyExported BOOLEAN, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentListId), CONSTRAINT C_NEXT_LIST_ID_UNIQUE_FOR_PARENT UNIQUE (parentListId, nextListId) ); +CREATE TABLE PlaylistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, listId INTEGER, trackId INTEGER, databaseUuid TEXT, nextEntityId INTEGER, membershipReference INTEGER, CONSTRAINT C_NAME_UNIQUE_FOR_LIST UNIQUE (listId, databaseUuid, trackId), FOREIGN KEY (listId) REFERENCES Playlist (id) ON DELETE CASCADE ); +CREATE TABLE PreparelistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, trackId INTEGER, trackNumber INTEGER, FOREIGN KEY (trackId) REFERENCES Track (id) ON DELETE CASCADE ); +CREATE TABLE Smartlist ( listUuid TEXT NOT NULL PRIMARY KEY, title TEXT, parentPlaylistPath TEXT, nextPlaylistPath TEXT, nextListUuid TEXT, rules TEXT, lastEditTime DATETIME, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentPlaylistPath), CONSTRAINT C_NEXT_LIST_UNIQUE_FOR_PARENT UNIQUE (parentPlaylistPath, nextPlaylistPath, nextListUuid) ); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('Information',1); +CREATE INDEX index_AlbumArt_hash ON AlbumArt (hash); +CREATE TRIGGER trigger_after_insert_Pack_timestamp AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.lastPackTime IS NULL BEGIN UPDATE Pack SET lastPackTime = strftime('%s') WHERE ROWID = NEW.ROWID; END; +CREATE TRIGGER trigger_after_insert_Pack_changeLogId AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.changeLogId = 0 BEGIN UPDATE Pack SET changeLogId = 1 WHERE ROWID = NEW.ROWID; END; +CREATE VIEW ChangeLog (id, trackId) AS SELECT 0, 0 WHERE FALSE; +CREATE INDEX index_Track_filename ON Track (filename); +CREATE INDEX index_Track_albumArtId ON Track (albumArtId); +CREATE INDEX index_Track_uri ON Track (uri); +CREATE INDEX index_Track_title ON Track(title); +CREATE INDEX index_Track_length ON Track(length); +CREATE INDEX index_Track_rating ON Track(rating); +CREATE INDEX index_Track_year ON Track(year); +CREATE INDEX index_Track_dateAdded ON Track(dateAdded); +CREATE INDEX index_Track_genre ON Track(genre); +CREATE INDEX index_Track_artist ON Track(artist); +CREATE INDEX index_Track_album ON Track(album); +CREATE INDEX index_Track_key ON Track(key); +CREATE INDEX index_Track_bpmAnalyzed ON Track(CAST(bpmAnalyzed + 0.5 AS int)); +CREATE TRIGGER trigger_after_insert_Track_check_id AFTER INSERT ON Track WHEN NEW.id <= (SELECT seq FROM sqlite_sequence WHERE name = 'Track') BEGIN SELECT RAISE(ABORT, 'Recycling deleted track id''s are not allowed'); END; +CREATE TRIGGER trigger_after_update_Track_check_Id BEFORE UPDATE ON Track WHEN NEW.id <> OLD.id BEGIN SELECT RAISE(ABORT, 'Changing track id''s are not allowed'); END; +CREATE TRIGGER trigger_after_insert_Track_fix_origin AFTER INSERT ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END; +CREATE TRIGGER trigger_after_update_Track_fix_origin AFTER UPDATE ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END; +CREATE TRIGGER trigger_after_update_Track_timestamp AFTER UPDATE OF length, bpm, year, filename, bitrate, bpmAnalyzed, albumArtId, title, artist, album, genre, comment, label, composer, remixer, key, rating, albumArt, fileType, isAnalyzed, isBeatgridLocked, trackData, overviewWaveformData, beatData, quickCues, loops, explicitLyrics, activeOnLoadLoops ON Track FOR EACH ROW BEGIN UPDATE Track SET lastEditTime = strftime('%s') WHERE ROWID=NEW.ROWID; END; +CREATE VIEW PerformanceData AS SELECT id AS trackId, isAnalyzed, trackData, overviewWaveFormData, beatData, quickCues, loops, thirdPartySourceId, activeOnLoadLoops FROM Track; +CREATE TRIGGER trigger_instead_update_thirdPartySourceId_PerformanceData INSTEAD OF UPDATE OF thirdPartySourceId ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET thirdPartySourceId = NEW.thirdPartySourceId WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_insert_PerformanceData INSTEAD OF INSERT ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NEW.isAnalyzed, trackData = NEW.trackData, overviewWaveFormData = NEW.overviewWaveFormData, beatData = NEW.beatData, quickCues = NEW.quickCues, loops = NEW.loops, thirdPartySourceId = NEW.thirdPartySourceId, activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_isAnalyzed_PerformanceData INSTEAD OF UPDATE OF isAnalyzed ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NEW.isAnalyzed WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_trackData_PerformanceData INSTEAD OF UPDATE OF trackData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET trackData = NEW.trackData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_overviewWaveFormData_PerformanceData INSTEAD OF UPDATE OF overviewWaveFormData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET overviewWaveFormData = NEW.overviewWaveFormData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_beatData_PerformanceData INSTEAD OF UPDATE OF beatData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET beatData = NEW.beatData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_quickCues_PerformanceData INSTEAD OF UPDATE OF quickCues ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET quickCues = NEW.quickCues WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_loops_PerformanceData INSTEAD OF UPDATE OF loops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET loops = NEW.loops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_delete_PerformanceData INSTEAD OF DELETE ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NULL, trackData = NULL, overviewWaveFormData = NULL, beatData = NULL, quickCues = NULL, loops = NULL, thirdPartySourceId = NULL WHERE Track.id = OLD.trackId; END; +CREATE TRIGGER trigger_instead_update_activeOnLoadLoops_PerformanceData INSTEAD OF UPDATE OF activeOnLoadLoops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_before_insert_List BEFORE INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = -(1 + nextListId) WHERE nextListId = NEW.nextListId AND parentListId = NEW.parentListId; END; +CREATE TRIGGER trigger_after_insert_List AFTER INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = NEW.id WHERE nextListId = -(1 + NEW.nextListId) AND parentListId = NEW.parentListId; END; +CREATE TRIGGER trigger_after_delete_List AFTER DELETE ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = OLD.nextListId WHERE nextListId = OLD.id; DELETE FROM Playlist WHERE parentListId = OLD.id; END; +CREATE TRIGGER trigger_after_update_isPersistParent AFTER UPDATE ON Playlist WHEN (old.isPersisted = 0 AND new.isPersisted = 1) OR (old.parentListId != new.parentListId AND new.isPersisted = 1) BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END; +CREATE TRIGGER trigger_after_update_isPersistChild AFTER UPDATE ON Playlist WHEN old.isPersisted = 1 AND new.isPersisted = 0 BEGIN UPDATE Playlist SET isPersisted = 0 WHERE id IN (SELECT childListId FROM PlaylistAllChildren WHERE id=new.id); END; +CREATE TRIGGER trigger_after_insert_isPersist AFTER INSERT ON Playlist WHEN new.isPersisted = 1 BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END; +CREATE VIEW PlaylistAllParent AS WITH FindAllParent AS ( SELECT id, parentListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.parentListId FROM Playlist Plist INNER JOIN FindAllParent recursiveCTE ON recursiveCTE.parentListId = Plist.id ) SELECT * FROM FindAllParent; +CREATE VIEW PlaylistAllChildren AS WITH FindAllChild AS ( SELECT id, id as childListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.id FROM Playlist Plist INNER JOIN FindAllChild recursiveCTE ON recursiveCTE.childListId = Plist.parentListId ) SELECT * FROM FindAllChild WHERE id <> childListId; +CREATE VIEW PlaylistPath AS WITH RECURSIVE Heirarchy AS ( SELECT id AS child, parentListId AS parent, title AS name, 1 AS depth FROM Playlist UNION ALL SELECT child, parentListId AS parent, title AS name, h.depth + 1 AS depth FROM Playlist c JOIN Heirarchy h ON h.parent = c.id ORDER BY depth DESC ), OrderedList AS ( SELECT id , nextListId, 1 AS position FROM Playlist WHERE nextListId = 0 UNION ALL SELECT c.id , c.nextListId , l.position + 1 FROM Playlist c INNER JOIN OrderedList l ON c.nextListId = l.id ), NameConcat AS ( SELECT child AS id, GROUP_CONCAT(name ,';') || ';' AS path FROM ( SELECT child, name FROM Heirarchy ORDER BY depth DESC ) GROUP BY child ) SELECT id, path, ROW_NUMBER() OVER ( ORDER BY (SELECT COUNT(*) FROM (SELECT * FROM Heirarchy WHERE child = id) ) DESC, (SELECT position FROM OrderedList ol WHERE ol.id = c.id) ASC ) AS position FROM Playlist c LEFT JOIN NameConcat g USING (id); +CREATE INDEX index_PlaylistEntity_nextEntityId_listId ON PlaylistEntity(nextEntityId, listId); +CREATE TRIGGER trigger_before_delete_PlaylistEntity BEFORE DELETE ON PlaylistEntity WHEN OLD.trackId > 0 BEGIN UPDATE PlaylistEntity SET nextEntityId = OLD.nextEntityId WHERE nextEntityId = OLD.id AND listId = OLD.listId; END; +CREATE INDEX index_PreparelistEntity_trackId ON PreparelistEntity (trackId); +COMMIT; diff --git a/testdata/ref/engine/sc5000/firmware-4.0.1/Database2/m.db.sql b/testdata/ref/engine/sc5000/firmware-4.0.1/Database2/m.db.sql new file mode 100644 index 0000000..d907f27 --- /dev/null +++ b/testdata/ref/engine/sc5000/firmware-4.0.1/Database2/m.db.sql @@ -0,0 +1,59 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE Information ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, schemaVersionMajor INTEGER, schemaVersionMinor INTEGER, schemaVersionPatch INTEGER, currentPlayedIndiciator INTEGER, lastRekordBoxLibraryImportReadCounter INTEGER); +INSERT INTO Information VALUES(1,'26accd6f-d569-4f15-94b0-b06ccae7794c',2,21,2,-7534864215177630487,NULL); +CREATE TABLE AlbumArt ( id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT, albumArt BLOB ); +CREATE TABLE Pack ( id INTEGER PRIMARY KEY AUTOINCREMENT, packId TEXT, changeLogDatabaseUuid TEXT, changeLogId INTEGER, lastPackTime DATETIME ); +CREATE TABLE Track ( id INTEGER PRIMARY KEY AUTOINCREMENT, playOrder INTEGER, length INTEGER, bpm INTEGER, year INTEGER, path TEXT, filename TEXT, bitrate INTEGER, bpmAnalyzed REAL, albumArtId INTEGER, fileBytes INTEGER, title TEXT, artist TEXT, album TEXT, genre TEXT, comment TEXT, label TEXT, composer TEXT, remixer TEXT, key INTEGER, rating INTEGER, albumArt TEXT, timeLastPlayed DATETIME, isPlayed BOOLEAN, fileType TEXT, isAnalyzed BOOLEAN, dateCreated DATETIME, dateAdded DATETIME, isAvailable BOOLEAN, isMetadataOfPackedTrackChanged BOOLEAN, isPerfomanceDataOfPackedTrackChanged BOOLEAN, playedIndicator INTEGER, isMetadataImported BOOLEAN, pdbImportKey INTEGER, streamingSource TEXT, uri TEXT, isBeatGridLocked BOOLEAN, originDatabaseUuid TEXT, originTrackId INTEGER, trackData BLOB, overviewWaveFormData BLOB, beatData BLOB, quickCues BLOB, loops BLOB, thirdPartySourceId INTEGER, streamingFlags INTEGER, explicitLyrics BOOLEAN, activeOnLoadLoops INTEGER, lastEditTime DATETIME, CONSTRAINT C_originDatabaseUuid_originTrackId UNIQUE (originDatabaseUuid, originTrackId), CONSTRAINT C_path UNIQUE (path), FOREIGN KEY (albumArtId) REFERENCES AlbumArt (id) ON DELETE RESTRICT ); +CREATE TABLE Playlist ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, parentListId INTEGER, isPersisted BOOLEAN, nextListId INTEGER, lastEditTime DATETIME, isExplicitlyExported BOOLEAN, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentListId), CONSTRAINT C_NEXT_LIST_ID_UNIQUE_FOR_PARENT UNIQUE (parentListId, nextListId) ); +CREATE TABLE PlaylistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, listId INTEGER, trackId INTEGER, databaseUuid TEXT, nextEntityId INTEGER, membershipReference INTEGER, CONSTRAINT C_NAME_UNIQUE_FOR_LIST UNIQUE (listId, databaseUuid, trackId), FOREIGN KEY (listId) REFERENCES Playlist (id) ON DELETE CASCADE ); +CREATE TABLE PreparelistEntity ( id INTEGER PRIMARY KEY AUTOINCREMENT, trackId INTEGER, trackNumber INTEGER, FOREIGN KEY (trackId) REFERENCES Track (id) ON DELETE CASCADE ); +CREATE TABLE Smartlist ( listUuid TEXT NOT NULL PRIMARY KEY, title TEXT, parentPlaylistPath TEXT, nextPlaylistPath TEXT, nextListUuid TEXT, rules TEXT, lastEditTime DATETIME, CONSTRAINT C_NAME_UNIQUE_FOR_PARENT UNIQUE (title, parentPlaylistPath), CONSTRAINT C_NEXT_LIST_UNIQUE_FOR_PARENT UNIQUE (parentPlaylistPath, nextPlaylistPath, nextListUuid) ); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('Information',1); +CREATE INDEX index_AlbumArt_hash ON AlbumArt (hash); +CREATE TRIGGER trigger_after_insert_Pack_timestamp AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.lastPackTime IS NULL BEGIN UPDATE Pack SET lastPackTime = strftime('%s') WHERE ROWID = NEW.ROWID; END; +CREATE TRIGGER trigger_after_insert_Pack_changeLogId AFTER INSERT ON Pack FOR EACH ROW WHEN NEW.changeLogId = 0 BEGIN UPDATE Pack SET changeLogId = 1 WHERE ROWID = NEW.ROWID; END; +CREATE VIEW ChangeLog (id, trackId) AS SELECT 0, 0 WHERE FALSE; +CREATE INDEX index_Track_filename ON Track (filename); +CREATE INDEX index_Track_albumArtId ON Track (albumArtId); +CREATE INDEX index_Track_uri ON Track (uri); +CREATE INDEX index_Track_title ON Track(title); +CREATE INDEX index_Track_length ON Track(length); +CREATE INDEX index_Track_rating ON Track(rating); +CREATE INDEX index_Track_year ON Track(year); +CREATE INDEX index_Track_dateAdded ON Track(dateAdded); +CREATE INDEX index_Track_genre ON Track(genre); +CREATE INDEX index_Track_artist ON Track(artist); +CREATE INDEX index_Track_album ON Track(album); +CREATE INDEX index_Track_key ON Track(key); +CREATE INDEX index_Track_bpmAnalyzed ON Track(CAST(bpmAnalyzed + 0.5 AS int)); +CREATE TRIGGER trigger_after_insert_Track_check_id AFTER INSERT ON Track WHEN NEW.id <= (SELECT seq FROM sqlite_sequence WHERE name = 'Track') BEGIN SELECT RAISE(ABORT, 'Recycling deleted track id''s are not allowed'); END; +CREATE TRIGGER trigger_after_update_Track_check_Id BEFORE UPDATE ON Track WHEN NEW.id <> OLD.id BEGIN SELECT RAISE(ABORT, 'Changing track id''s are not allowed'); END; +CREATE TRIGGER trigger_after_insert_Track_fix_origin AFTER INSERT ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END; +CREATE TRIGGER trigger_after_update_Track_fix_origin AFTER UPDATE ON Track WHEN IFNULL(NEW.originTrackId, 0) = 0 OR IFNULL(NEW.originDatabaseUuid, '') = '' BEGIN UPDATE Track SET originTrackId = NEW.id, originDatabaseUuid = (SELECT uuid FROM Information) WHERE track.id = NEW.id; END; +CREATE TRIGGER trigger_after_update_Track_timestamp AFTER UPDATE OF length, bpm, year, filename, bitrate, bpmAnalyzed, albumArtId, title, artist, album, genre, comment, label, composer, remixer, key, rating, albumArt, fileType, isAnalyzed, isBeatgridLocked, trackData, overviewWaveformData, beatData, quickCues, loops, explicitLyrics, activeOnLoadLoops ON Track FOR EACH ROW BEGIN UPDATE Track SET lastEditTime = strftime('%s') WHERE ROWID=NEW.ROWID; END; +CREATE VIEW PerformanceData AS SELECT id AS trackId, isAnalyzed, trackData, overviewWaveFormData, beatData, quickCues, loops, thirdPartySourceId, activeOnLoadLoops FROM Track; +CREATE TRIGGER trigger_instead_update_thirdPartySourceId_PerformanceData INSTEAD OF UPDATE OF thirdPartySourceId ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET thirdPartySourceId = NEW.thirdPartySourceId WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_insert_PerformanceData INSTEAD OF INSERT ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = IFNULL(NEW.isAnalyzed, isAnalyzed), trackData = NEW.trackData, overviewWaveFormData = NEW.overviewWaveFormData, beatData = NEW.beatData, quickCues = NEW.quickCues, loops = NEW.loops, thirdPartySourceId = NEW.thirdPartySourceId, activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_isAnalyzed_PerformanceData INSTEAD OF UPDATE OF isAnalyzed ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NEW.isAnalyzed WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_trackData_PerformanceData INSTEAD OF UPDATE OF trackData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET trackData = NEW.trackData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_overviewWaveFormData_PerformanceData INSTEAD OF UPDATE OF overviewWaveFormData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET overviewWaveFormData = NEW.overviewWaveFormData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_beatData_PerformanceData INSTEAD OF UPDATE OF beatData ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET beatData = NEW.beatData WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_quickCues_PerformanceData INSTEAD OF UPDATE OF quickCues ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET quickCues = NEW.quickCues WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_update_loops_PerformanceData INSTEAD OF UPDATE OF loops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET loops = NEW.loops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_instead_delete_PerformanceData INSTEAD OF DELETE ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET isAnalyzed = NULL, trackData = NULL, overviewWaveFormData = NULL, beatData = NULL, quickCues = NULL, loops = NULL, thirdPartySourceId = NULL WHERE Track.id = OLD.trackId; END; +CREATE TRIGGER trigger_instead_update_activeOnLoadLoops_PerformanceData INSTEAD OF UPDATE OF activeOnLoadLoops ON PerformanceData FOR EACH ROW BEGIN UPDATE Track SET activeOnLoadLoops = NEW.activeOnLoadLoops WHERE Track.id = NEW.trackId; END; +CREATE TRIGGER trigger_before_insert_List BEFORE INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = -(1 + nextListId) WHERE nextListId = NEW.nextListId AND parentListId = NEW.parentListId; END; +CREATE TRIGGER trigger_after_insert_List AFTER INSERT ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = NEW.id WHERE nextListId = -(1 + NEW.nextListId) AND parentListId = NEW.parentListId; END; +CREATE TRIGGER trigger_after_delete_List AFTER DELETE ON Playlist FOR EACH ROW BEGIN UPDATE Playlist SET nextListId = OLD.nextListId WHERE nextListId = OLD.id; DELETE FROM Playlist WHERE parentListId = OLD.id; END; +CREATE TRIGGER trigger_after_update_isPersistParent AFTER UPDATE ON Playlist WHEN (old.isPersisted = 0 AND new.isPersisted = 1) OR (old.parentListId != new.parentListId AND new.isPersisted = 1) BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END; +CREATE TRIGGER trigger_after_update_isPersistChild AFTER UPDATE ON Playlist WHEN old.isPersisted = 1 AND new.isPersisted = 0 BEGIN UPDATE Playlist SET isPersisted = 0 WHERE id IN (SELECT childListId FROM PlaylistAllChildren WHERE id=new.id); END; +CREATE TRIGGER trigger_after_insert_isPersist AFTER INSERT ON Playlist WHEN new.isPersisted = 1 BEGIN UPDATE Playlist SET isPersisted = 1 WHERE id IN (SELECT parentListId FROM PlaylistAllParent WHERE id=new.id); END; +CREATE VIEW PlaylistAllParent AS WITH FindAllParent AS ( SELECT id, parentListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.parentListId FROM Playlist Plist INNER JOIN FindAllParent recursiveCTE ON recursiveCTE.parentListId = Plist.id ) SELECT * FROM FindAllParent; +CREATE VIEW PlaylistAllChildren AS WITH FindAllChild AS ( SELECT id, id as childListId FROM Playlist UNION ALL SELECT recursiveCTE.id, Plist.id FROM Playlist Plist INNER JOIN FindAllChild recursiveCTE ON recursiveCTE.childListId = Plist.parentListId ) SELECT * FROM FindAllChild WHERE id <> childListId; +CREATE VIEW PlaylistPath AS WITH RECURSIVE Heirarchy AS ( SELECT id AS child, parentListId AS parent, title AS name, 1 AS depth FROM Playlist UNION ALL SELECT child, parentListId AS parent, title AS name, h.depth + 1 AS depth FROM Playlist c JOIN Heirarchy h ON h.parent = c.id ORDER BY depth DESC ), OrderedList AS ( SELECT id , nextListId, 1 AS position FROM Playlist WHERE nextListId = 0 UNION ALL SELECT c.id , c.nextListId , l.position + 1 FROM Playlist c INNER JOIN OrderedList l ON c.nextListId = l.id ), NameConcat AS ( SELECT child AS id, GROUP_CONCAT(name ,';') || ';' AS path FROM ( SELECT child, name FROM Heirarchy ORDER BY depth DESC ) GROUP BY child ) SELECT id, path, ROW_NUMBER() OVER ( ORDER BY (SELECT COUNT(*) FROM (SELECT * FROM Heirarchy WHERE child = id) ) DESC, (SELECT position FROM OrderedList ol WHERE ol.id = c.id) ASC ) AS position FROM Playlist c LEFT JOIN NameConcat g USING (id); +CREATE INDEX index_PlaylistEntity_nextEntityId_listId ON PlaylistEntity(nextEntityId, listId); +CREATE TRIGGER trigger_before_delete_PlaylistEntity BEFORE DELETE ON PlaylistEntity WHEN OLD.trackId > 0 BEGIN UPDATE PlaylistEntity SET nextEntityId = OLD.nextEntityId WHERE nextEntityId = OLD.id AND listId = OLD.listId; END; +CREATE INDEX index_PreparelistEntity_trackId ON PreparelistEntity (trackId); +COMMIT;