Commit 6973dff5 authored by Hugo Beauzée-Luyssen's avatar Hugo Beauzée-Luyssen
Browse files

Label: Allow arbitrary entities to be linked to a label

Fix #421
parent 3701ee6f
Pipeline #229253 passed with stage
in 43 seconds
......@@ -30,6 +30,7 @@
#include "Media.h"
#include "database/SqliteTools.h"
#include "database/SqliteQuery.h"
#include "utils/Enums.h"
namespace medialibrary
{
......@@ -66,10 +67,11 @@ const std::string& Label::name() const
Query<IMedia> Label::media()
{
static const std::string req = "FROM " + Media::Table::Name + " f "
"INNER JOIN " + FileRelationTable::Name + " lfr ON lfr.media_id = f.id_media "
"WHERE lfr.label_id = ?";
return make_query<Media, IMedia>( m_ml, "f.*", req, "", m_id );
static const std::string req = "FROM " + Media::Table::Name + " m "
"INNER JOIN " + FileRelationTable::Name + " lfr ON lfr.entity_id = m.id_media "
"WHERE lfr.label_id = ? "
"AND lfr.entity_type = " + utils::enum_to_string( EntityType::Media );
return make_query<Media, IMedia>( m_ml, "m.*", req, "", m_id );
}
LabelPtr Label::create( MediaLibraryPtr ml, const std::string& name )
......@@ -81,20 +83,32 @@ LabelPtr Label::create( MediaLibraryPtr ml, const std::string& name )
return self;
}
std::string Label::schema( const std::string& tableName, uint32_t )
std::string Label::schema( const std::string& tableName, uint32_t dbModel )
{
if ( tableName == FileRelationTable::Name )
{
if ( dbModel < 37 )
{
return "CREATE TABLE " + FileRelationTable::Name +
"("
"label_id INTEGER,"
"media_id INTEGER,"
"PRIMARY KEY(label_id,media_id),"
"FOREIGN KEY(label_id) "
"REFERENCES " + Table::Name + "(id_label) ON DELETE CASCADE,"
"FOREIGN KEY(media_id) "
"REFERENCES " + Media::Table::Name + "(id_media) ON DELETE CASCADE"
")";
}
return "CREATE TABLE " + FileRelationTable::Name +
"("
"label_id INTEGER,"
"media_id INTEGER,"
"PRIMARY KEY(label_id,media_id),"
"FOREIGN KEY(label_id) "
"REFERENCES " + Table::Name + "(id_label) ON DELETE CASCADE,"
"FOREIGN KEY(media_id) "
"REFERENCES " + Media::Table::Name + "(id_media) ON DELETE CASCADE"
")";
"("
"label_id INTEGER,"
"entity_id INTEGER,"
"entity_type INTEGER,"
"PRIMARY KEY(label_id,entity_id,entity_type),"
"FOREIGN KEY(label_id) "
"REFERENCES " + Table::Name + "(id_label) ON DELETE CASCADE"
")";
}
assert( tableName == Table::Name );
return "CREATE TABLE " + Table::Name +
......@@ -106,22 +120,41 @@ std::string Label::schema( const std::string& tableName, uint32_t )
std::string Label::trigger( Triggers trigger, uint32_t dbModel )
{
assert( trigger == Triggers::DeleteFts );
return "CREATE TRIGGER " + triggerName( trigger, dbModel ) +
" BEFORE DELETE ON " + Table::Name +
" BEGIN"
" UPDATE " + Media::FtsTable::Name +
" SET labels = TRIM(REPLACE(labels, old.name, ''))"
" WHERE labels MATCH old.name;"
" END";
switch ( trigger )
{
case Triggers::DeleteFts:
return "CREATE TRIGGER " + triggerName( trigger, dbModel ) +
" BEFORE DELETE ON " + Table::Name +
" BEGIN"
" UPDATE " + Media::FtsTable::Name +
" SET labels = TRIM(REPLACE(labels, old.name, ''))"
" WHERE labels MATCH old.name;"
" END";
case Triggers::DeleteMediaLabel:
return "CREATE TRIGGER " + triggerName( trigger, dbModel ) +
" AFTER DELETE ON " + Media::Table::Name +
" BEGIN"
" DELETE FROM " + FileRelationTable::Name +
" WHERE entity_type = " +
utils::enum_to_string( EntityType::Media ) +
" AND entity_id = old.id_media;"
" END";
}
return "<invalid request>";
}
std::string Label::triggerName( Triggers trigger, uint32_t )
std::string Label::triggerName( Triggers trigger, uint32_t dbModel )
{
UNUSED_IN_RELEASE( trigger );
assert( trigger == Triggers::DeleteFts );
return "delete_label_fts";
UNUSED_IN_RELEASE( dbModel );
switch ( trigger )
{
case Triggers::DeleteFts:
return "delete_label_fts";
case Triggers::DeleteMediaLabel:
assert( dbModel >= 37 );
return "label_delete_media";
}
return "<invalid trigger>";
}
std::string Label::index( Label::Indexes index, uint32_t dbModel )
......@@ -130,6 +163,7 @@ std::string Label::index( Label::Indexes index, uint32_t dbModel )
{
case Indexes::MediaId:
assert( dbModel >= 34 );
assert( dbModel < 37 );
return "CREATE INDEX " + indexName( index, dbModel ) +
" ON " + FileRelationTable::Name + "(media_id)";
}
......@@ -143,6 +177,7 @@ std::string Label::indexName( Label::Indexes index, uint32_t dbModel )
{
case Indexes::MediaId:
assert( dbModel >= 34 );
assert( dbModel < 37 );
return "label_rel_media_id_idx";
}
return "<invalid request>";
......@@ -152,19 +187,20 @@ bool Label::checkDbModel( MediaLibraryPtr ml )
{
OPEN_READ_CONTEXT( ctx, ml->getConn() );
auto checkTrigger = []( Triggers t ){
return sqlite::Tools::checkTriggerStatement(
trigger( t, Settings::DbModelVersion ),
triggerName( t, Settings::DbModelVersion ) );
};
return sqlite::Tools::checkTableSchema(
schema( Table::Name, Settings::DbModelVersion ),
Table::Name ) &&
sqlite::Tools::checkTableSchema(
schema( FileRelationTable::Name, Settings::DbModelVersion ),
FileRelationTable::Name ) &&
sqlite::Tools::checkTriggerStatement(
trigger( Triggers::DeleteFts, Settings::DbModelVersion ),
triggerName( Triggers::DeleteFts, Settings::DbModelVersion ) ) &&
sqlite::Tools::checkIndexStatement(
index( Indexes::MediaId, Settings::DbModelVersion ),
indexName( Indexes::MediaId, Settings::DbModelVersion ) );
checkTrigger( Triggers::DeleteFts ) &&
checkTrigger( Triggers::DeleteMediaLabel );
}
void Label::createTable( sqlite::Connection* dbConnection )
......@@ -181,12 +217,8 @@ void Label::createTriggers( sqlite::Connection* dbConnection )
{
sqlite::Tools::executeRequest( dbConnection,
trigger( Triggers::DeleteFts, Settings::DbModelVersion ) );
}
void Label::createIndexes( sqlite::Connection* dbConnection )
{
sqlite::Tools::executeRequest( dbConnection,
index( Indexes::MediaId, Settings::DbModelVersion ) );
trigger( Triggers::DeleteMediaLabel, Settings::DbModelVersion ) );
}
}
......@@ -46,10 +46,15 @@ public:
enum class Triggers : uint8_t
{
DeleteFts,
DeleteMediaLabel,
};
enum class Indexes : uint8_t
{
MediaId,
MediaId, // Removed in model 37
};
enum class EntityType : uint8_t
{
Media,
};
Label( MediaLibraryPtr ml, sqlite::Row& row );
......@@ -69,7 +74,6 @@ public:
static bool checkDbModel( MediaLibraryPtr ml );
static void createTable( sqlite::Connection* dbConnection );
static void createTriggers( sqlite::Connection* dbConnection );
static void createIndexes( sqlite::Connection* dbConnection );
private:
MediaLibraryPtr m_ml;
......
......@@ -300,8 +300,8 @@ Query<ILabel> Media::labels() const
{
static const std::string req = "FROM " + Label::Table::Name + " l "
"INNER JOIN " + Label::FileRelationTable::Name + " lfr ON lfr.label_id = l.id_label "
"WHERE lfr.media_id = ?";
return make_query<Label, ILabel>( m_ml, "l.*", req, "", m_id );
"WHERE lfr.entity_id = ? AND entity_type = ?";
return make_query<Label, ILabel>( m_ml, "l.*", req, "", m_id, Label::EntityType::Media );
}
uint32_t Media::playCount() const
......@@ -2139,8 +2139,9 @@ bool Media::addLabel( LabelPtr label )
{
auto t = m_ml->getConn()->newTransaction();
std::string req = "INSERT INTO " + Label::FileRelationTable::Name + " VALUES(?, ?)";
if ( sqlite::Tools::executeInsert( m_ml->getConn(), req, label->id(), m_id ) == 0 )
std::string req = "INSERT INTO " + Label::FileRelationTable::Name + " VALUES(?, ?, ?)";
if ( sqlite::Tools::executeInsert( m_ml->getConn(), req, label->id(),
m_id, Label::EntityType::Media ) == 0 )
return false;
const std::string reqFts = "UPDATE " + Media::FtsTable::Name + " "
"SET labels = labels || ' ' || ? WHERE rowid = ?";
......@@ -2167,8 +2168,10 @@ bool Media::removeLabel( LabelPtr label )
{
auto t = m_ml->getConn()->newTransaction();
std::string req = "DELETE FROM " + Label::FileRelationTable::Name + " WHERE label_id = ? AND media_id = ?";
if ( sqlite::Tools::executeDelete( m_ml->getConn(), req, label->id(), m_id ) == false )
std::string req = "DELETE FROM " + Label::FileRelationTable::Name +
" WHERE label_id = ? AND entity_id = ? AND entity_type = ?";
if ( sqlite::Tools::executeDelete( m_ml->getConn(), req, label->id(),
m_id, Label::EntityType::Media ) == false )
return false;
const std::string reqFts = "UPDATE " + Media::FtsTable::Name + " "
"SET labels = TRIM(REPLACE(labels, ?, '')) WHERE rowid = ?";
......
......@@ -499,7 +499,6 @@ void MediaLibrary::createAllTriggers()
parser::Task::createIndex( dbConn );
Bookmark::createIndexes( dbConn );
Chapter::createIndexes( dbConn );
Label::createIndexes( dbConn );
}
bool MediaLibrary::checkDatabaseIntegrity()
......@@ -2080,6 +2079,13 @@ void MediaLibrary::migrateModel36to37()
sqlite::Connection::WeakDbContext weakConnCtx{ dbConn };
auto t = dbConn->newTransaction();
std::string reqs[] = {
# include "database/migrations/migration36-37.sql"
};
for ( const auto& req : reqs )
sqlite::Tools::executeRequest( dbConn, req );
m_settings.setDbModelVersion( 37 );
t->commit();
}
......
"CREATE TEMPORARY TABLE " + Label::FileRelationTable::Name + "_backup"
"("
"label_id INTEGER,"
"media_id INTEGER,"
"PRIMARY KEY(label_id,media_id)"
")",
"INSERT INTO " + Label::FileRelationTable::Name + "_backup "
"SELECT * FROM " + Label::FileRelationTable::Name,
"DROP TABLE " + Label::FileRelationTable::Name,
Label::schema( Label::FileRelationTable::Name, 37 ),
"INSERT INTO " + Label::FileRelationTable::Name +
" SELECT label_id, media_id, " + utils::enum_to_string( Label::EntityType::Media ) +
" FROM " + Label::FileRelationTable::Name + "_backup",
"DROP TABLE " + Label::FileRelationTable::Name + "_backup",
Label::trigger( Label::Triggers::DeleteMediaLabel, 37 ),
......@@ -72,6 +72,7 @@ namespace
"increment_media_nb_playlist", "insert_album_fts", "insert_artist_fts",
"insert_folder_fts", "insert_genre_fts", "insert_media_fts",
"insert_playlist_fts", "insert_show_fts",
"label_delete_media",
"media_cascade_file_deletion",
"media_cascade_file_update",
"media_group_decrement_nb_media_on_deletion",
......@@ -114,7 +115,6 @@ namespace
"folder_device_id_idx",
"index_last_played_date",
"index_media_presence",
"label_rel_media_id_idx",
"media_album_track_idx",
"media_artist_id_idx",
"media_duration_idx",
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment