-- ============================================================ -- Migration 1.7.0 - Materialisation de la vue sessiondebriefs -- ============================================================ -- Cible : MariaDB 10.4+. -- Idempotent : peut etre rejoue sans erreur. Le cache est entierement -- reconstruit a chaque execution (TRUNCATE + backfill). -- -- Changement principal : la vue sessiondebriefs etait recalculee a chaque -- requete (12 LEFT JOIN + GROUP BY + sous-requetes correlees). Elle est -- desormais materialisee dans une vraie table session_debriefs_cache, -- peuplee : -- - en bulk au moment de cette migration (backfill toutes sessions), -- - puis par une procedure stockee rebuild_debrief_cache(p_session_id) -- appelee depuis le code PHP a chaque modification de session -- (session/stop, session/userleave, session/updateobjectives, -- events/storetriggerevent, events/storereactevent). -- -- La vue sessiondebriefs garde son nom et ses colonnes : le code consommateur -- (stats_object.php : getResultsForSession, getUserHistory) ne change pas. -- Elle devient un simple passe-plat sur la table cache. -- -- Action 2 (fix partiel du double JOIN participates) : la jointure PH -- (qui sert a determiner TargetRoleId/TargetRole) est desormais contrainte -- sur PH.userId = R.hitUserId au lieu de matcher tous les participants. -- Effet : -- * Pour les hits user-on-user : meme resultat (correct par construction). -- * Pour les hits NPC/objets : TargetRoleId vaut 3 (IA) au lieu d'un -- role aleatoire d'un autre participant. C'est un fix de bug. -- La jointure PS reste inchangee (preserve le fallback ShooterId quand T est NULL). -- ============================================================ USE proserveapi; -- ------------------------------------------------------------ -- 1) Table de cache (creation idempotente) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS `session_debriefs_cache` ( `SessionId` int(11) NOT NULL, `SessionTypeId` int(11) DEFAULT NULL, `SessionType` varchar(255) DEFAULT NULL, `SessionName` varchar(255) DEFAULT NULL, `SessionDate` datetime DEFAULT NULL, `MapName` varchar(255) DEFAULT NULL, `ScenarioName` varchar(255) DEFAULT NULL, `SessionSuccessful` tinyint(1) DEFAULT NULL, `SessionDuration` float DEFAULT NULL, `TriggerTypeId` int(11) DEFAULT NULL, `TriggerType` varchar(255) DEFAULT NULL, `ShooterId` int(11) DEFAULT NULL, `ShooterName` varchar(255) DEFAULT NULL, `ShooterRoleId` int(11) DEFAULT NULL, `ShooterRole` varchar(255) DEFAULT NULL, `ShotIndex` int(11) DEFAULT NULL, `ReactId` int(11) DEFAULT NULL, `ReactModeId` int(11) DEFAULT NULL, `ReactMode` varchar(255) DEFAULT NULL, `ReactTypeId` int(11) DEFAULT NULL, `ReactType` varchar(255) DEFAULT NULL, `TargetUserId` int(11) DEFAULT NULL, `TargetUserName` varchar(255) DEFAULT NULL, `TargetRoleId` int(11) DEFAULT NULL, `TargetRole` varchar(255) DEFAULT NULL, `TargetName` varchar(255) DEFAULT NULL, `TargetBoneName` varchar(255) DEFAULT NULL, `TargetKilled` int(4) DEFAULT NULL, `HitLocationX` double DEFAULT NULL, `HitLocationY` double DEFAULT NULL, `HitLocationTag` varchar(255) DEFAULT NULL, `HitPrecision` double DEFAULT NULL, `HitTargetDistance` double DEFAULT NULL, `ReactionTime` double DEFAULT NULL, `TimeStamp` double DEFAULT NULL, `NbHit` bigint(21) DEFAULT NULL, `NbKilled` bigint(21) DEFAULT NULL, KEY `idx_sdc_session` (`SessionId`), KEY `idx_sdc_session_shooter` (`SessionId`, `ShooterId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- ------------------------------------------------------------ -- 2) Procedure stockee pour reconstruire le cache d'une session -- ------------------------------------------------------------ DROP PROCEDURE IF EXISTS rebuild_debrief_cache; DELIMITER $$ CREATE PROCEDURE rebuild_debrief_cache(IN p_session_id INT) BEGIN DELETE FROM session_debriefs_cache WHERE SessionId = p_session_id; INSERT INTO session_debriefs_cache ( SessionId, SessionTypeId, SessionType, SessionName, SessionDate, MapName, ScenarioName, SessionSuccessful, SessionDuration, TriggerTypeId, TriggerType, ShooterId, ShooterName, ShooterRoleId, ShooterRole, ShotIndex, ReactId, ReactModeId, ReactMode, ReactTypeId, ReactType, TargetUserId, TargetUserName, TargetRoleId, TargetRole, TargetName, TargetBoneName, TargetKilled, HitLocationX, HitLocationY, HitLocationTag, HitPrecision, HitTargetDistance, ReactionTime, TimeStamp, NbHit, NbKilled ) SELECT S.id, S.sessionType, ST.displayName, S.sessionName, S.sessionDate, S.mapName, S.scenarioName, S.success, S.timeToFinish, COALESCE(T.type, -1), COALESCE(TT.displayName, ''), IFNULL(T.srcUserId, PS.userId), US.username, IFNULL(PS.role, 3), URS.displayName, COALESCE(T.indexCount, -1), COALESCE(R.id, -1), COALESCE(R.reactMode, 2), COALESCE(RM.displayName, ''), COALESCE(R.reactType, -1), COALESCE(RT.displayName, ''), COALESCE(R.hitUserId, -1), COALESCE(UH.username, ''), IFNULL(PH.role, 3), URT.displayName, COALESCE(R.hitTargetName, ''), COALESCE(R.hitBoneName, ''), COALESCE(R.targetKilled, 0), COALESCE(R.objectHitLocationX, 0), COALESCE(R.objectHitLocationY, 0), COALESCE(R.objectHitTagLocation, ''), COALESCE(R.hitPrecision, 0), COALESCE(R.distance, 0), COALESCE(R.reactTime, 0), COALESCE(R.timeStamp, 0), COUNT(DISTINCT R.id), COUNT(DISTINCT RK.srcEventIndex, RK.hitTargetName) FROM sessions S LEFT JOIN participates PS ON (S.id = PS.sessionId) LEFT JOIN triggerevents T ON (S.id = T.sessionId) LEFT JOIN triggereventtypes TT ON (TT.id = T.type) LEFT JOIN sessiontypes ST ON (ST.id = S.sessionType) LEFT JOIN reactevents R ON (T.indexCount = R.srcEventIndex AND T.sessionId = R.srcEventSessionId) LEFT JOIN users UH ON (UH.id = R.hitUserId) LEFT JOIN users US ON (US.id = T.srcUserId OR US.id = PS.userId) LEFT JOIN reacteventtypes RT ON (RT.id = R.reactType) LEFT JOIN reacteventmodes RM ON (RM.id = COALESCE(R.reactMode, 2)) LEFT JOIN reactevents RK ON (R.id = RK.id AND RK.targetKilled = 1) -- PH : Action 2 du fix - PH represente la participation de l'utilisateur cible -- (au lieu de "n'importe quel participant") pour que TargetRole soit coherent. LEFT JOIN participates PH ON (S.id = PH.sessionId AND PH.userId = R.hitUserId) LEFT JOIN userroles URS ON (URS.id = IFNULL(PS.role, 3)) LEFT JOIN userroles URT ON (URT.id = IFNULL(PH.role, 3)) WHERE S.id = p_session_id GROUP BY S.id, IFNULL(T.srcUserId, PS.userId), COALESCE(T.indexCount, -1), COALESCE(R.hitTargetName, '') ORDER BY S.id, IFNULL(T.srcUserId, PS.userId), COALESCE(T.indexCount, -1), COALESCE(R.id, -1); END$$ DELIMITER ; -- ------------------------------------------------------------ -- 3) Backfill complet : recalcule le cache pour toutes les sessions existantes -- ------------------------------------------------------------ TRUNCATE TABLE session_debriefs_cache; INSERT INTO session_debriefs_cache ( SessionId, SessionTypeId, SessionType, SessionName, SessionDate, MapName, ScenarioName, SessionSuccessful, SessionDuration, TriggerTypeId, TriggerType, ShooterId, ShooterName, ShooterRoleId, ShooterRole, ShotIndex, ReactId, ReactModeId, ReactMode, ReactTypeId, ReactType, TargetUserId, TargetUserName, TargetRoleId, TargetRole, TargetName, TargetBoneName, TargetKilled, HitLocationX, HitLocationY, HitLocationTag, HitPrecision, HitTargetDistance, ReactionTime, TimeStamp, NbHit, NbKilled ) SELECT S.id, S.sessionType, ST.displayName, S.sessionName, S.sessionDate, S.mapName, S.scenarioName, S.success, S.timeToFinish, COALESCE(T.type, -1), COALESCE(TT.displayName, ''), IFNULL(T.srcUserId, PS.userId), US.username, IFNULL(PS.role, 3), URS.displayName, COALESCE(T.indexCount, -1), COALESCE(R.id, -1), COALESCE(R.reactMode, 2), COALESCE(RM.displayName, ''), COALESCE(R.reactType, -1), COALESCE(RT.displayName, ''), COALESCE(R.hitUserId, -1), COALESCE(UH.username, ''), IFNULL(PH.role, 3), URT.displayName, COALESCE(R.hitTargetName, ''), COALESCE(R.hitBoneName, ''), COALESCE(R.targetKilled, 0), COALESCE(R.objectHitLocationX, 0), COALESCE(R.objectHitLocationY, 0), COALESCE(R.objectHitTagLocation, ''), COALESCE(R.hitPrecision, 0), COALESCE(R.distance, 0), COALESCE(R.reactTime, 0), COALESCE(R.timeStamp, 0), COUNT(DISTINCT R.id), COUNT(DISTINCT RK.srcEventIndex, RK.hitTargetName) FROM sessions S LEFT JOIN participates PS ON (S.id = PS.sessionId) LEFT JOIN triggerevents T ON (S.id = T.sessionId) LEFT JOIN triggereventtypes TT ON (TT.id = T.type) LEFT JOIN sessiontypes ST ON (ST.id = S.sessionType) LEFT JOIN reactevents R ON (T.indexCount = R.srcEventIndex AND T.sessionId = R.srcEventSessionId) LEFT JOIN users UH ON (UH.id = R.hitUserId) LEFT JOIN users US ON (US.id = T.srcUserId OR US.id = PS.userId) LEFT JOIN reacteventtypes RT ON (RT.id = R.reactType) LEFT JOIN reacteventmodes RM ON (RM.id = COALESCE(R.reactMode, 2)) LEFT JOIN reactevents RK ON (R.id = RK.id AND RK.targetKilled = 1) LEFT JOIN participates PH ON (S.id = PH.sessionId AND PH.userId = R.hitUserId) LEFT JOIN userroles URS ON (URS.id = IFNULL(PS.role, 3)) LEFT JOIN userroles URT ON (URT.id = IFNULL(PH.role, 3)) GROUP BY S.id, IFNULL(T.srcUserId, PS.userId), COALESCE(T.indexCount, -1), COALESCE(R.hitTargetName, '') ORDER BY S.id, IFNULL(T.srcUserId, PS.userId), COALESCE(T.indexCount, -1), COALESCE(R.id, -1); -- ------------------------------------------------------------ -- 4) La vue devient un passe-plat sur la table cache -- ------------------------------------------------------------ CREATE OR REPLACE VIEW sessiondebriefs AS SELECT * FROM session_debriefs_cache;